-
Notifications
You must be signed in to change notification settings - Fork 21.5k
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
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks #25337
Conversation
/cc @matthewd @rafaelfranca This is not ready to merge, but the remaining changes I have to make are to commit messages and where the deprecation warning is emitted, which shouldn't affect the discussion around this change. |
end | ||
|
||
# Alias for `changed?` | ||
def has_changes_to_save? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't changes_to_be_saved?
be more coherent with attribute_change_to_be_saved
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal was to make the difference between some of the methods more clear. Do you think that model.changes_to_be_saved?
reads clearly enough? Compared to model.has_changes_to_save?
I feel like I need to wrap my mind around the name changes in the public API outside of the code to fully focus on their semantics (I hope that's not weird and distracting from the PR convo). I hope this helps. Legend:
@sgrif Please feel free to edit the above to correct any incorrect meaning. |
Are those aliases intended to be public? Wasn't sure. If they are public, I agree, they were sort of hard to wrap my head around. |
636dc49
to
80574c9
Compare
…ve) callbacks We pretty frequently get bug reports that "dirty is broken inside of after callbacks". Intuitively they are correct. You'd expect `Model.after_save { puts changed? }; model.save` to do the same thing as `model.save; puts model.changed?`, but it does not. However, changing this goes much farther than just making the behavior more intuitive. There are a _ton_ of places inside of AR that can be drastically simplified with this change. Specifically, autosave associations, timestamps, touch, counter cache, and just about anything else in AR that works with callbacks have code to try to avoid "double save" bugs which we will be able to flat out remove with this change. We introduce two new sets of methods, both with names that are meant to be more explicit than dirty. The first set maintains the old behavior, and their names are meant to center that they are about changes that occurred during the save that just happened. They are equivalent to `previous_changes` when called outside of after callbacks, or once the deprecation cycle moves. The second set is the new behavior. Their names imply that they are talking about changes from the database representation. The fact that this is what we really care about became clear when looking at `BelongsTo.touch_record` when tests were failing. I'm unsure that this set of methods should be in the public API. Outside of after callbacks, they are equivalent to the existing methods on dirty. Dirty itself is not deprecated, nor are the methods inside of it. They will only emit the warning when called inside of after callbacks. The scope of this breakage is pretty large, but the migration path is simple. Given how much this can improve our codebase, and considering that it makes our API more intuitive, I think it's worth doing.
9e2c387
to
16ae3db
Compare
Going to move forward with this change. If anyone has suggested improvements to the names of the new methods, please open a PR. |
With the changes in #25337, double save bugs are pretty much impossible, so we can just lift this restriction with pretty much no change. There were a handful of cases where we were relying on specific quirks in tests that had to be updated. The change to has_one associations was due to a particularly interesting test where an autosaved has_one association was replaced with a new child, where the child failed to save but the test wanted to check that the parent id persisted to `nil`. I think this is almost certainly the wrong behavior, and I may change that behavior later. But ultimately the root cause was because we never remove the parent in memory when nullifying the child. This makes #23197 no longer needed, but it is what we'll do to fix some issues on 5.0 Close #23197
@sgrif what happened to i can't figure the new naming. this looks ugly to me though: why we don't make
then we can use it `after_save :analyze_file_async, if: :file_was_updated? |
Are the new method names documented anywhere besides here? Maybe update http://api.rubyonrails.org/classes/ActiveModel/Dirty.html . |
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
5.1 added new methods for checking changes, specifically in before callbacks. Summary: 5.0 => 5.1 name_changed? => will_save_change_to_name? (before callback) name_changed? => saved_change_to_name? (after callback) A larger table of changes can be found here: rails/rails#25337 (comment) Use latest ancestry with changes for the changed change: https://github.com/stefankroes/ancestry/pull/441/files
see rails/rails#25337. Basically User.name_changed? behaved differently in after_save callbacks then after the save finished. This was confusing. So now there are two distinct methods: * User.will_save_change_to_name - to use before the save * User.saved_change_to_name? - to use in after_save callbacks This way it is clear what the function is supposed to mean.
rails/rails#25337 Note: most probably methods in the previous version weren't exactly correct. Note: there are still ~20 broken tests
This was deprecated in Rails 5.1 and removed in Rails 5.2. Specifically, according to the deprecation message: > The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To resolve this, we simply update everywhere that we're using one of these methods inside an `after` callback to instead use one of the new methods. See rails/rails#25337 for full context.
This was deprecated in Rails 5.1 and removed in Rails 5.2. Specifically, according to the deprecation message: > The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To resolve this, we simply update everywhere that we're using one of these methods inside an `after` callback to instead use one of the new methods. See rails/rails#25337 for full context.
…ehaviour See details in rails/rails#25337
This commit reflects changes in ActiveRecord::Dirty API version 5.1 Ref: #710, heartcombo/devise#4574, rails/rails#25337
This commit reflects changes in ActiveRecord::Dirty API version 5.1 Ref: #710, heartcombo/devise#4574, rails/rails#25337
This commit reflects changes in ActiveRecord::Dirty API version 5.1 Ref: #710, heartcombo/devise#4574, rails/rails#25337
This commit reflects changes in ActiveRecord::Dirty API version 5.1 Ref: #710, heartcombo/devise#4574, rails/rails#25337
We pretty frequently get bug reports that "dirty is broken inside of after callbacks". Intuitively they are correct. You'd expect
Model.after_save { puts changed? }; model.save
to do the same thing asmodel.save; puts model.changed?
, but it does not.However, changing this goes much farther than just making the behavior more intuitive. There are a ton of places inside of AR that can be drastically simplified with this change. Specifically, autosave associations, timestamps, touch, counter cache, and just about anything else in AR that works with callbacks have code to try to avoid "double save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to be more explicit than dirty. The first set maintains the old behavior, and their names are meant to center that they are about changes that occurred during the save that just happened. They are equivalent to
previous_changes
when called outside of after callbacks, or once the deprecation cycle moves.The second set is the new behavior. Their names imply that they are talking about changes from the database representation. The fact that this is what we really care about became clear when looking at
BelongsTo.touch_record
when tests were failing. I'm unsure that this set of methods should be in the public API. Outside of after callbacks, they are equivalent to the existing methods on dirty.I am not married to any of the method names. Please bikeshed the shit out of them, I am open to alternatives.
Dirty itself is not deprecated, nor are the methods inside of it. They will only emit the warning when called inside of after callbacks. The scope of this breakage is pretty large, but the migration path is simple. Given how much this can improve our codebase, and considering that it makes our API more intuitive, I think it's worth doing.
Unresolved questions
Do we want the "new behavior" methods to be in the public API at all? They are straight aliases to the existing methods in dirty after 5.2/6.0. However, since we get these bug reports, someone probably does want the new behavior today.
Still left todo
I need to improve the commit messages, and move the deprecation warning up to the caller of
mutation_tracker
to include the exact method to call instead. The current implementation is also emitting deprecation warnings in places where the calls are valid. This will be fixed by moving the warning to the right place.