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
Undefine attribute methods of descendants when resetting column information #31475
Undefine attribute methods of descendants when resetting column information #31475
Conversation
Thanks for the pull request, and welcome! The Rails team is excited to review your changes, and you should hear from @georgeclaghorn (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. This repository is being automatically checked for code quality issues using Code Climate. You can see results for this analysis in the PR status below. Newly introduced issues should be fixed before a Pull Request is considered ready to review. Please see the contribution instructions for more information. |
So I'm investigating the history of this issue, and commit eecfa84 explains a lot. @jonleighton wrote in the commit message:
This is actually what is happening now, because the change he made to I think the correct fix is to do something similar to what was done in this commit, except rather than defining attribute methods only on the base class, instead modify I'll push another commit or two doing this, but I'd like to have a discussion about this because I think the current code is broken. When you |
I've added a failing spec in 6c474c3 demonstrating the problem. This will fail even with the change here since I'm only undefining methods on descendants, not ancestors up to base. But I'd like to discuss what the spec should be here, because what the tests say, and what the code actually does, are inconsistent. To recap, when you initialize a class (or call At this point, descendants will see the methods (because they are defined on a module included in an ancestor), but their However, when you call I think the best solution is to make |
Correction: This means that calling But the situation with the instance variables described above remains: if we e.g. reset column information and add a new column to a class in the heirarchy, descendants will never get it defined, so you fall through to |
6c474c3
to
d213772
Compare
After thinking about this a while, I've come to the conclusion that the safest thing is just to fix My fix is to update the line calling ([base_class] + base_class.descendants).each(&:undefine_attribute_methods) For Let me know if this sounds reasonable. I think there's more that can be done but this is a reasonable first step. |
If I'm reading this right, what you're describing is approximately true of a number of collection-shaped class attributes where descendants append to the inherited collection: semantically they're adding to (or sometimes otherwise mutating) the current value from the ancestor... but in practice the inherited value just gets copied upon the first mutation, and future changes in the ancestor's value go un-noticed. |
Yes, and that's fine I suppose in many cases, but in this particular case with I think the fix is simple here; I'm not implying we should go through all cases of class attributes and update all descendants. Where I'm going with this, for reference, is trying to remove reliance on the |
I haven't fully absorbed all the information you've given here yet, let alone then thought about it for a while... but my quick gut reaction is "do we want I'll concede that you'd need a pretty serious object hierarchy for it to make a practical difference, but at first glance, ISTM we shouldn't need to concern ourselves with other branches of the tree -- just those that affect or are affected by the target class. Actually, considering what the other lines in |
But if we're resetting column information, this affects all branches of the tree, no? i.e. Just to clarify/repeat: the starting point for this PR is that the test as it stands on master says it tests that reset column information "resets children". But actually after resetting on the parent (base class) the child is dispatching to method missing for the newly-added column, which to me means it was not actually reset.
Not really sure... I just assumed since it's a public method on a class, that's a valid use case. |
Yeah, I think my questions about affected classes and reasonable (This is more me thinking out loud than anything else -- I'm not specifically asking you for answers 😅) At the moment, it feels like it's just not considering the possibility: it deals with the schema via the .. which brings me back to "does this method make sense for non-base classes, and if so, what does it mean?" Either way, I definitely agree that this is a bug: the test you've added should pass. Right now I think I'm just equivocating on whether |
I see your point now and agree, it doesn't really make sense to call |
d213772
to
8fa9a8f
Compare
If we don't do this, then we end up with an inconsistent situation where a parent class may e.g. reset column information, but child classes will contine to see attribute methods as already generated, and thus not pick up this new column (falling through to method_missing).
25204d0
to
48cd586
Compare
I went ahead and changed the line to use |
a597b2e
to
5b8afb2
Compare
5b8afb2
to
876865a
Compare
Related: #22057 |
Incidentally, not sure it's relevant, but commit 9deb6ab by @sgrif which fixes #22057 has two tests and one code change. The code change is: diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index a9bd094a66..e3f304b0af 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -339,6 +339,9 @@ def reload_schema_from_cache
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ direct_descendants.each do |descendant|
+ descendant.send(:reload_schema_from_cache)
+ end
end However, while the first test fails if this change is reversed on master, the second one (the one I've changed in this PR) currently passes even without the code change. It also passes with the updated test here. |
@matthewd I'd really like to get this merged, is there anything else I need to do? My long-winded comments above maybe made this seem more complicated than it really is. We agree that the updated spec here should pass. I think we also agree that the fix is to call I don't like leaving PRs behind that get buried under newer ones and just become stale. If this won't get merged, I'll close it. |
Yep, sounds fair, thanks 👍🏻 |
Thanks! 😄 |
I'm not quite sure if this is the right solution, but while looking through these tests I notice that the test checking that "reset column information resets children" is not really correct. I've modified the test to show that it fails in the sense that when column information is changed on the parent, the child does not reset its attribute methods, and thus although the (current) test passes, the method is actually falling through to
method_missing
, which I don't believe it should do.My fix is to undefine attribute methods on all descendants when they are undefined on the parent, which passes this and other tests. But mostly I'd just like to clarify the expected behaviour.