Skip to content

Add raise_on_unknown_attributes configuration option#22

Closed
JulienBURNET-FAUCHE wants to merge 17 commits intomasterfrom
feature/add-raise-on-unknown-attributes
Closed

Add raise_on_unknown_attributes configuration option#22
JulienBURNET-FAUCHE wants to merge 17 commits intomasterfrom
feature/add-raise-on-unknown-attributes

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)

pimpin and others added 17 commits January 6, 2025 15:33
NB : in AR, the cast is done at assignment time. Lets try to do it.
TODO : fix move casting stuff from serialize and place them in cast obviously
And keep unchanged the serialize_value needed for queries (ie find_by_some_time)
- Remove double casting in create_setters to avoid type inconsistencies
- Fix Timestamp type precision by applying floor() during cast
- Fix DateTimeWith3Decimal precision handling in both cast and serialize

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…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 introduces a significant enhancement to how CouchbaseOrm models handle attributes that are not explicitly defined in their schema. By adding a new raise_on_unknown_attributes configuration option, models can now be configured to either strictly enforce schema definitions by raising an error for unknown attributes (the default behavior) or to gracefully ignore them while logging a warning. This flexibility is particularly beneficial for applications interacting with evolving external APIs, managing legacy data with deprecated fields, or ensuring backward compatibility during schema migrations, allowing for more resilient and adaptable data handling.

Highlights

  • New Configuration Option: Introduces raise_on_unknown_attributes as a class attribute in CouchbaseOrm::Document, defaulting to true. This option controls how the model behaves when encountering attributes not explicitly defined.
  • Flexible Unknown Attribute Handling: When raise_on_unknown_attributes is set to false, the model will no longer raise an ActiveModel::UnknownAttributeError for undefined attributes during initialization or mass assignment. Instead, it will log a warning and silently ignore these attributes.
  • attribute_writer_missing Implementation: Overrides attribute_writer_missing to conditionally handle unknown attribute assignments. If the new configuration is false, it logs a warning; otherwise, it defers to the default ActiveModel behavior (raising an error).
  • assign_attributes Modification: The assign_attributes method in CouchbaseOrm::Persistence is updated to filter out unknown attributes when raise_on_unknown_attributes is false, preventing them from being passed to the super method.
  • Comprehensive Test Coverage: Adds a new test class and a suite of tests to verify the behavior of raise_on_unknown_attributes in both its default (raising error) and disabled (ignoring and logging) states during model initialization and attribute assignment.
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 handling document attributes. The implementation is solid and is accompanied by a good set of tests. However, I've identified a few areas for improvement. There's a potential issue with the module inclusion order in base.rb that could affect dirty attribute tracking. Additionally, the override for assign_attributes should be moved to ensure consistent behavior across all document types. Finally, there's an unrelated and undocumented breaking change in the Timestamp type that truncates timestamps, which should be reverted and addressed in a separate pull request.

Comment on lines +36 to 37
include Changeable
include ::ActiveModel::Attributes

Choose a reason for hiding this comment

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

high

The change in module inclusion order appears to introduce a bug. ActiveModel::Attributes includes ActiveModel::Dirty. By including Changeable before ActiveModel::Attributes, the methods in ActiveModel::Dirty (like changes_applied) will override the ones in Changeable. The original code and its comment indicated that Changeable is intended to override ActiveModel::Dirty's behavior. To restore the correct override behavior, Changeable should be included after ActiveModel::Attributes.

        include ::ActiveModel::Attributes
        include Changeable

Comment on lines 4 to 10
def cast(value)
return nil if value.nil?
return Time.at(value) if value.is_a?(Integer) || value.is_a?(Float)
return Time.at(value.to_i) if value.is_a?(String) && value =~ /^[0-9]+$/
return value.utc if value.is_a?(Time)
super(value).utc
return Time.at(value).floor if value.is_a?(Integer) || value.is_a?(Float)
return Time.at(value.to_i).floor if value.is_a?(String) && value =~ /^[0-9]+$/
return value.utc.floor if value.is_a?(Time)
super(value).utc.floor
end

Choose a reason for hiding this comment

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

high

The addition of .floor to the return values in the cast method truncates timestamps to the nearest second, effectively removing fractional seconds. This is a significant, backward-incompatible change that is not documented in the pull request description and is unrelated to the primary goal of this PR. This could lead to data loss for applications that rely on sub-second precision. It's recommended to revert this change and submit it as a separate pull request with a clear explanation of its impact and motivation.

            def cast(value)
              return nil if value.nil?
              return Time.at(value) if value.is_a?(Integer) || value.is_a?(Float)
              return Time.at(value.to_i) if value.is_a?(String) && value =~ /^[0-9]+$/
              return value.utc if value.is_a?(Time)
              super(value).utc
            end

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.

medium

This assign_attributes override is located in the Persistence module, which is only included in CouchbaseOrm::Base. This means it won't apply to CouchbaseOrm::NestedDocument, leading to inconsistent behavior for handling unknown attributes between different document types. This method should be moved to the CouchbaseOrm::Document class in lib/couchbase-orm/base.rb to ensure it applies to all document types inheriting from it.

@JulienBURNET-FAUCHE
Copy link
Collaborator Author

Closing this PR as it was created from the wrong branch. Opening a new PR from master.

@JulienBURNET-FAUCHE JulienBURNET-FAUCHE deleted the feature/add-raise-on-unknown-attributes branch October 28, 2025 16:44
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.

2 participants