Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement CurrentAttributes with AttributeMethods #50676

Closed

Conversation

seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Jan 9, 2024

Motivation / Background

The ActiveSupport::CurrentAttributes implementation copies a lot of the same code provided by the ActiveModel::AttributeMethods concern.

Backport Active{Model,Support}::AttributeMethods

First, migrate ActiveModel::AttributeMethods (along with its corresponding test coverage) to ActiveSupport::AttributeMethods.

Next, implement ActiveModel::AttributeMethods in terms of ActiveSupport::AttributeMethods, including a configuration to retain ActiveModel::MissingAttributeError.

Implement ActiveSupport::CurrentAttributes with ActiveSupport::AttributeMethods.

Replace Code Generator blocks with
ActiveSupport::AttributeMethods-generated methods like attribute(name) and attribute=(name, value).

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Changes that are unrelated should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

Backport `Active{Model,Support}::AttributeMethods`
---

First, migrate `ActiveModel::AttributeMethods` (along with its
corresponding test coverage) to `ActiveSupport::AttributeMethods`.

Next, implement `ActiveModel::AttributeMethods` in terms of
`ActiveSupport::AttributeMethods`, including a configuration to retain
`ActiveModel::MissingAttributeError`.

Implement `ActiveSupport::CurrentAttributes` with `ActiveSupport::AttributeMethods`.
---

Replace Code Generator blocks with
`ActiveSupport::AttributeMethods`-generated methods like
`attribute(name)` and `attribute=(name, value)`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As an alternative to introducing this as a concern that's part of the public API, we could mark this :nodoc: to treat it as an implementation detail of ActiveModel::AttributeMethods.

Comment on lines -110 to +112
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes) do |batch|
batch <<
"def #{name}" <<
"attributes[:#{name}]" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch|
batch <<
"def #{name}=(value)" <<
"attributes[:#{name}] = value" <<
"end"
end
end
end

ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}" <<
"instance.#{name}" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}=(value)" <<
"instance.#{name} = value" <<
"end"
end
end
end
names.each { |name| define_attribute_methods name }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@byroot could you review these changes, since you're in this block's git blame? I'm curious about how to re-implement the delegation method definitions. At the moment, the tests pass, but rely on the first #method_missing to handle the delegation.

Copy link
Member

Choose a reason for hiding this comment

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

At the moment, the tests pass, but rely on the first #method_missing to handle the delegation.

YOu mean from the class to the instance? Yeah define method (or eval) inside method_missing is a terrible pattern I wish to eliminate.

I'll set a reminder to look at your PR tomorrow.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, but we were eagerly defining the delegators for the attributes, it's only for unexpected method that we lazy defined them (which is less bad but not great).

I think you can simply call delegate right after the line we're commenting, and you can use the private as: argument to get optimized delegation, should produce about the same result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@byroot I think I understand your suggestion in principle, but I'm struggling to craft the statement.

I'm trying this, but it's failing:

delegate name, "#{name}=", to: :instance, as: singleton_class
# => activesupport/lib/active_support/core_ext/module/delegation.rb:220:in `public_instance_method': undefined method `world' for class `#<Class:CurrentAttributesTest::Current>' (NameError)
# 
#             receiver_class.public_instance_method(method)
#                           ^^^^^^^^^^^^^^^^^^^^^^^

Copy link
Member

Choose a reason for hiding this comment

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

I'm trying this, but it's failing:

as: take the class of the object that is supposed to receive the call, so here it would have been as: self.

Copy link
Member

Choose a reason for hiding this comment

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

Actually if you still to want to reduce duplication in that module, you can do the delegate part only, that would do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@byroot I've opened #50685 to cherry-pick that change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah define method (or eval) inside method_missing is a terrible pattern I wish to eliminate.

@byroot I've explored an alternative to method missing in #50686.

@rafaelfranca
Copy link
Member

rafaelfranca commented Jan 9, 2024

Thank you for the pull request.

I don't think this change makes sense. The code that is supposedly duplicated is smaller than the entirety of the new ActiveSupport::AttributeMethods that was extracted.

I don't think this extraction is worth.

seanpdoyle added a commit to seanpdoyle/rails that referenced this pull request Jan 9, 2024
Follow-up to [rails#50676][]

Instead of relying on code generation, call a corresponding [delegate][]
method on the `.singleton_class`.

[rails#50676]: rails#50676
[delegate]: https://edgeapi.rubyonrails.org/classes/Module.html#method-i-delegate
seanpdoyle added a commit to seanpdoyle/rails that referenced this pull request Jan 9, 2024
Follow-up to [rails#50676][]

Instead of relying on code generation, call a corresponding [delegate][]
method on the `.singleton_class`.

[rails#50676]: rails#50676
[delegate]: https://edgeapi.rubyonrails.org/classes/Module.html#method-i-delegate
seanpdoyle added a commit to seanpdoyle/rails that referenced this pull request Jan 9, 2024
Follow-up to [rails#50676][]

Instead of relying on code generation, call a corresponding [delegate][]
method on the `.singleton_class`.

[rails#50676]: rails#50676
[delegate]: https://edgeapi.rubyonrails.org/classes/Module.html#method-i-delegate

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants