Skip to content

Conversation

@seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Oct 24, 2025

Prior to this commit, the respond_to_missing? implementation had a
conditional to supports attributes being able to be written to through
methods "did not respond to" (like send("#{attr_name}=", value)), as
well as ?-suffixed predicate methods.

The method_missing implementation has a set of conditionals similar to
respond_to_missing, but with subtle variations through checking for an
attribute name's inclusion in known_attributes (attributes defined by
the Schema or assigned during Base#load) rather than the attribute
name's inclusion in attributes (the current instance's assigned
values, without any attributes declared in the Schema).

Without the implementation changes, the following tests introduced in
this commit fail:

  1) Failure:
BaseTest#test_respond_to_known_attributes [test/cases/base_test.rb:972]:
Expected #<Person:0x000000011d0cdcf8 @attributes={}, @prefix_options={}, @persisted=false> (Person) to respond to #name=.

This commit changes the respond_to_missing? conditional to check for
the presence of the key in known_attributes rather than attributes
so that instances will respond to =-suffixed writers and ?-suffixed
predicates for attributes that are declared in the resource's Schema
that have not yet been assigned to.

seanpdoyle added a commit to seanpdoyle/activeresource that referenced this pull request Oct 24, 2025
By including the [ActiveModel::AttributeAssignment][], the `Base` class
can access the [assign_attributes][] method for bulk assignment of
attributes **without** saving them to the server (like through
`Base#update_attributes`).

```ruby
person = Person.new

person.id   # => nil
person.name # => nil

person.assign_attributes id: 1, name: "Matz"

person.id   # => 1
person.name # => "Matz"
```

Support for versions prior to 7.2.0
---

This commit includes a conditional monkey-patch of the
`_assign_attribute` method when Active Model's version is < 7.2.0:

```ruby
  # 7.1.5.2
  def _assign_attribute(k, v)
    setter = :"#{k}="
    if respond_to?(setter)
      public_send(setter, v)
    else
      raise UnknownAttributeError.new(self, k.to_s)
    end
  end

  # 7.2.0
  def _assign_attribute(k, v)
    setter = :"#{k}="
    public_send(setter, v)
  rescue NoMethodError
    if respond_to?(setter)
      raise
    else
      raise UnknownAttributeError.new(self, k.to_s)
    end
  end
```

The change is necessary because the [7.1.5.2][] version queries the
instance with `respond_to?`, whereas the [7.2.0][] version skips the
query and sends the method first, falling back to a `NoMethodError`
rescue in case that it doesn't.

[rails#450][] is an alternative to including the monkey patch. If merged,
it'd make `respond_to?` behavior more like `method_missing`, so the
monkey patch would be unnecessary.

[ActiveModel::AttributeAssignment]: https://edgeapi.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html
[assign_attributes]: https://edgeapi.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html#method-i-assign_attributes
[7.1.5.2]: https://github.com/rails/rails/blob/v7.1.5.2/activemodel/lib/active_model/attribute_assignment.rb
[7.2.0]: https://github.com/rails/rails/blob/v7.2.0/activemodel/lib/active_model/attribute_assignment.rb
[rails#450]: rails#450
@seanpdoyle seanpdoyle force-pushed the method-missing-regex branch from f1e2ce5 to 662f562 Compare October 24, 2025 16:40
@seanpdoyle seanpdoyle changed the title Base: Replace regexps in method_missing and respond_to_missing Base: read from known_attributes in respond_to_missing? Oct 24, 2025
Prior to this commit, the `respond_to_missing?` implementation had a
conditional to supports attributes being able to be written to through
methods "did not respond to" (like `send("#{attr_name}=", value)`), as
well as `?`-suffixed predicate methods.

The `method_missing` implementation has a set of conditionals similar to
`respond_to_missing`, but with subtle variations through checking for an
attribute name's inclusion in `known_attributes` (attributes defined by
the Schema **or** assigned during `Base#load`) rather than the attribute
name's inclusion in `attributes` (the current instance's assigned
values, without any attributes declared in the Schema).

Without the implementation changes, the following tests introduced in
this commit fail:

```
  1) Failure:
BaseTest#test_respond_to_known_attributes [test/cases/base_test.rb:972]:
Expected #<Person:0x000000011d0cdcf8 @attributes={}, @prefix_options={}, @persisted=false> (Person) to respond to #name=.
```

This commit changes the `respond_to_missing?` conditional to check for
the presence of the key in `known_attributes` rather than `attributes`
so that instances will respond to `=`-suffixed writers and `?`-suffixed
predicates for attributes that are declared in the resource's Schema
that have not yet been assigned to.
@seanpdoyle seanpdoyle force-pushed the method-missing-regex branch from 662f562 to ebcc2a5 Compare October 24, 2025 16:42
@rafaelfranca rafaelfranca merged commit bdb7f20 into rails:main Oct 24, 2025
19 checks passed
@seanpdoyle seanpdoyle deleted the method-missing-regex branch October 24, 2025 19:11
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