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

Ensure associations are inherited even after subclasses are defined #39473

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

kamipo
Copy link
Member

@kamipo kamipo commented May 29, 2020

This is a common class_attribute implementation issue (e.g. #39467,
#39468, #38871).

For now, we should take care of the change propagation by themselves if
we want to ensure the propagation after the class_attribute are
accessed in subclasses.

Fixes #20678.

This is a common `class_attribute` implementation issue (e.g. rails#39467,
rails#39468, rails#38871).

For now, we should take care of the change propagation by themselves if
we want to ensure the propagation after the `class_attribute` are
accessed in subclasses.

Fixes rails#20678.
I've partly picked `LiloHash` from rails#39487.

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
jonathanhefner added a commit to jonathanhefner/rails that referenced this pull request Jun 14, 2020
Comment on lines 127 to 129
if default.is_a?(Hash)
class_methods << inplace_updatable_class_attribute(name)
end
Copy link
Member

Choose a reason for hiding this comment

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

This is a very interesting idea! I originally chose the class_store approach because I didn't want to add (yet another) option to class_attribute, and because some options did not seem suitable (e.g. instance_writer: true). But I really like your approach, so I changed to the same!

descendants.each do |subclass|
next if subclass.#{name}.equal?(#{name})

if !subclass.#{name}.key?(key) || prev_value&.equal?(subclass.#{name}[key])
Copy link
Member

Choose a reason for hiding this comment

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

This can produce different outcomes for flyweight / frozen objects versus other objects. For example:

A = Class.new
A.class_attribute :things, default: {}
B = Class.new(A)

A.inplace_update_things(:point, [0, 0])
B.things = B.things.dup
B.inplace_update_things(:point, [0, 0])
A.inplace_update_things(:point, [1, 1])
B.things[:point]  # => [0, 0]

A.inplace_update_things(:x, 0)
B.things = B.things.dup
B.inplace_update_things(:x, 0)
A.inplace_update_things(:x, 1)
B.things[:x]  # => 1

My approach to fix this was to track overridden keys.

Copy link
Member Author

Choose a reason for hiding this comment

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

I know this does not perfectly detect overrides.
But I suppose this works well enough as a small utility that solves such like internal problems.

Copy link
Member Author

Choose a reason for hiding this comment

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

Note that this small utility is focused to avoid allocation as much as possible.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I am not a fan of extra allocation either. But I felt it was necessary because even if such problems are unlikely, they would be hard to diagnose when they did occur. Also, because it is implemented as an automatic feature of class_attribute, it seems more public, and so I wanted to make it more foolproof.

Copy link
Member

Choose a reason for hiding this comment

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

I added ff3e8f4 which should prevent Set allocation when the superclass's class_attribute value is empty? or undefined. If the update method is called on the superclass later (and its class_attribute value becomes non-empty), the subclass's Set will be allocated and properly back-filled.

Comment on lines +10 to +15
class LiloHash < Hash
def []=(key, value)
delete(key)
super
end
end
Copy link
Member

Choose a reason for hiding this comment

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

❤️

This reverts commit 5f0e546.

The purpose of the commit is to demonstrate how to generalize.

In this case, the generalize allocates extra `descendants` array so I've
reverted the commit.
@kamipo kamipo force-pushed the ensure_associations_inherited branch from 68aef08 to f153b8e Compare June 16, 2020 04:07
@rails-bot
Copy link

rails-bot bot commented Sep 14, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Sep 14, 2020
@kamipo kamipo removed the stale label Sep 18, 2020
@rails-bot
Copy link

rails-bot bot commented Dec 17, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Dec 17, 2020
@kamipo kamipo removed the stale label Dec 19, 2020
Base automatically changed from master to main January 14, 2021 17:01
@rails-bot
Copy link

rails-bot bot commented Apr 14, 2021

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Apr 14, 2021
@kamipo kamipo removed the stale label Apr 20, 2021
@rails-bot
Copy link

rails-bot bot commented Jul 19, 2021

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Jul 19, 2021
@kamipo kamipo removed the stale label Jul 21, 2021
@rails-bot
Copy link

rails-bot bot commented Oct 19, 2021

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Oct 19, 2021
@kamipo kamipo removed the stale label Oct 26, 2021
@rails-bot
Copy link

rails-bot bot commented Jan 24, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Jan 24, 2022
@rails-bot rails-bot bot closed this Jan 31, 2022
@kamipo kamipo reopened this Feb 26, 2022
@rails-bot rails-bot bot removed the stale label Feb 26, 2022
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.

Subclass misses association defined on parent
2 participants