Skip to content

Conversation

@JulienBURNET-FAUCHE
Copy link
Collaborator

Summary

Adds a new raise_on_unknown_attributes configuration option to control behavior when assigning unknown attributes to models.

Changes

  • Add class_attribute :raise_on_unknown_attributes (default: true) to Document class
  • Implement attribute_writer_missing method to handle unknown attributes
  • Modify assign_attributes in Persistence to filter unknown attributes when disabled
  • Add comprehensive test suite covering both enabled and disabled states

Usage

class User < CouchbaseOrm::Base
  self.raise_on_unknown_attributes = false
  attribute :name, :string
end

# No longer raises, just logs warning
User.new(name: 'John', unknown_field: 'value')

Motivation

Useful for:

  • Handling evolving external APIs with new fields
  • Managing legacy documents with deprecated attributes
  • Ensuring backward compatibility during schema changes

Tests

All tests pass (7 new examples, 0 failures)

…missing

- Add class_attribute :raise_on_unknown_attributes with default: true
- Implement attribute_writer_missing to handle unknown attributes
- When set to false, logs warning instead of raising exception
…attributes is false

- Modify assign_attributes to check raise_on_unknown_attributes setting
- Filter out unknown attributes before calling super when disabled
- Log warning for ignored attributes
- Add BaseTestWithUnknownAttributesAllowed test class
- Test behavior when raise_on_unknown_attributes is false
- Test default behavior (true) raises UnknownAttributeError
- Cover new, assign_attributes, and attribute storage scenarios
@gemini-code-assist
Copy link

Summary of Changes

Hello @JulienBURNET-FAUCHE, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the CouchbaseOrm library by providing a new configuration option, raise_on_unknown_attributes, to control how models react to the assignment of attributes not explicitly defined in their schema. This feature allows developers to choose between strict schema validation (the default behavior, raising an error) or a more lenient approach where unknown attributes are silently ignored with a warning. This flexibility is particularly beneficial for applications interacting with evolving external APIs, managing legacy data, or adapting to schema changes without immediate code updates.

Highlights

  • New Configuration Option: Introduced raise_on_unknown_attributes as a class attribute in CouchbaseOrm::Document, defaulting to true.
  • Flexible Attribute Handling: When raise_on_unknown_attributes is false, unknown attributes are silently ignored during assignment (e.g., new or assign_attributes), and a warning is logged instead of raising an ActiveModel::UnknownAttributeError.
  • Enhanced assign_attributes: The assign_attributes method in CouchbaseOrm::Persistence now conditionally filters out unknown attributes based on the new configuration, preventing errors for unmapped fields.
  • Comprehensive Testing: Added a new test class and a suite of 7 new examples to thoroughly cover both the default (raising error) and the new (ignoring with warning) behaviors for unknown attributes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable configuration option, raise_on_unknown_attributes, to provide more flexibility when dealing with evolving schemas. The implementation is solid and the tests are comprehensive. I have a couple of suggestions to improve consistency and performance. My main feedback is to move the assign_attributes override to the Document class to ensure consistent behavior across all model types, including NestedDocument. I've also suggested a minor refactoring for better performance and a style improvement for an unused variable. Overall, great work on this feature.

Comment on lines 156 to 170
def assign_attributes(hash)
hash = hash.with_indifferent_access if hash.is_a?(Hash)
super(hash.except("type"))

# Filter unknown attributes if raise_on_unknown_attributes is false
if !self.class.raise_on_unknown_attributes
known_attrs = hash.slice(*self.class.attribute_names).except("type")
unknown_attrs = hash.keys - self.class.attribute_names - ["type"]
if unknown_attrs.any?
CouchbaseOrm.logger.warn "Ignoring unknown attribute(s) for #{self.class.name}: #{unknown_attrs.join(', ')}"
end
super(known_attrs)
else
super(hash.except("type"))
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This override of assign_attributes is a good approach for handling unknown attributes during mass assignment. However, I have a couple of suggestions for improvement:

  1. Consistency: This method is defined in the Persistence module, which is included in CouchbaseOrm::Base but not in CouchbaseOrm::NestedDocument. This leads to inconsistent behavior, as NestedDocument will fall back to the default implementation and log unknown attributes one by one via attribute_writer_missing. To ensure consistent behavior for all models, I recommend moving this method override to the CouchbaseOrm::Document class in lib/couchbase-orm/base.rb.

  2. Performance and Readability: The implementation can be slightly optimized for better performance and clarity. The current approach for finding unknown attributes can be inefficient, and there's a redundant call to .except("type").

Here is a refactored version that addresses the performance/readability point. After applying this, you should also move the method as described above.

        def assign_attributes(hash)
            hash = hash.with_indifferent_access if hash.is_a?(Hash)

            if self.class.raise_on_unknown_attributes
                super(hash.except("type"))
            else
                # Filter unknown attributes
                known_names = self.class.attribute_names
                known_attrs = hash.slice(*known_names)

                # Use a Set for efficient lookup of unknown keys
                known_names_set = known_names.to_set
                unknown_keys = hash.keys.reject { |k| known_names_set.include?(k) || k == "type" }

                if unknown_keys.any?
                    CouchbaseOrm.logger.warn "Ignoring unknown attribute(s) for #{self.class.name}: #{unknown_keys.join(', ')}"
                end
                super(known_attrs)
            end
        end

Comment on lines +110 to +116
def attribute_writer_missing(name, value)
if self.class.raise_on_unknown_attributes
super
else
CouchbaseOrm.logger.warn "Ignoring unknown attribute '#{name}' for #{self.class.name}"
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value parameter is not used in this method. It's a good practice in Ruby to prefix unused parameters with an underscore (_) to signal this intent to other developers and to satisfy linters.

        def attribute_writer_missing(name, _value)
            if self.class.raise_on_unknown_attributes
                super
            else
                CouchbaseOrm.logger.warn "Ignoring unknown attribute '#{name}' for #{self.class.name}"
            end
        end

- Move assign_attributes override from Persistence to Document class
- Ensures consistent behavior for both Document and NestedDocument
- Use Set for efficient unknown attribute lookup (performance optimization)
- Add require 'set' to base.rb
- Remove duplicate implementation from persistence.rb

This addresses code review feedback about consistency and performance.
Copy link
Collaborator

@pimpin pimpin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems quite good. There are some missing coverage around nested docs.
I'd rather have implemented it by rescuing the error instead of monkey patching the implementation of AR internals.

…cument

- Add test classes: NestedDocWithUnknownAttributesAllowed and NestedDocWithDefaultBehavior
- Add parent document test classes to test nested behavior
- Test NestedDocument with raise_on_unknown_attributes = false
- Test NestedDocument with default behavior (true)
- Test nested documents within parent documents
- Test assign_attributes on NestedDocument
- All 14 tests pass (7 for base documents + 7 for nested documents)
- Add attribute_names_set class method with memoization
- Use cached Set in assign_attributes instead of converting on each call
- Eliminates O(m) Set conversion overhead, where m = number of attributes
- Maintains O(1) lookup performance for unknown attribute detection
- All 14 tests pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants