Rails 5.0 Rails 5.1+ changed? → saved_change_to? _was → _before_last_save changed? → saved_changes? changed_attributes → saved_changes.transform_values(&:first)
Model callbacks behavior After Rails 4.x release, some changes were made on callbacks to split update on the model before and after writing data to the database. We had to be careful to use the correct helpers: _changed? for after_save callbacks, and previous_changes[:] for after_commit callbacks:
# Rails 5.0
after_save :update_customer_details!, if: :status_changed?
after_commit :create_new_email_address!, if: -> { previous_changes[:email] }
With Rails 5.1, the code above becomes more coherent and even simpler to understand, as we can use the new helper saved_changed_to_ in both callbacks:
# Rails 5.1
after_save :update_customer_details!, if: :saved_changes_to_status?
after_commit :create_new_email_address!, if: :saved_changes_to_email?
Using the changed? helper along saved_change_to is now more comprehensible:
user = User.last
user.name # => "Bob"
user.name_changed? # => false
user.saved_change_to_name? # => false
user.name = "Clément" # => "Clément"
user.name_changed? # => true
user.saved_change_to_name? # => false
user.save # => true
user.name # => "Clément"
user.name_changed? # => false
user.saved_change_to_name? # => true
But let’s see what’s going on inside callbacks when they are triggered by placing breakpoints in our User model:
class User < ApplicationRecord
after_save { binding.pry }
after_commit { binding.pry }
end
user = User.last
user.name # => "Clément"
user.changed? # => false
user.saved_changes? # => false
user.name = "Bob" # => "Bob"
user.changed? # => true
user.saved_changes? # => false
user.save!
From: /Users/bob/Work/test/app/models/user.rb @ line 2 :
1: class User < ApplicationRecord
=> 2: after_save { binding.pry }
3: after_commit { binding.pry }
4: end
[1] > changed?
DEPRECATION WARNING: The behavior of `changed?` inside of after callbacks will
be changing in the next version of Rails. 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 maintain the current behavior, use `saved_changes?`
instead.
=> true
[2] > name_changed?
DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after
callbacks will be changing in the next version of Rails. 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 maintain the current behavior, use
`saved_change_to_attribute?` instead.
=> true
[3] > previous_changes
DEPRECATION WARNING: The behavior of `previous_changes` inside of after
callbacks is deprecated without replacement. In the next release of Rails, this
method inside of `after_save` will return the changes that were just saved.
=> {}
[4] > saved_change_to_name?
=> true
[5] > name_before_last_save
=> "Clément"
[6] > name
=> "Bob"
[6] > exit
From: /Users/bob/Work/test/app/models/user.rb @ line 3 :
1: class User < ApplicationRecord
2: after_save { binding.pry }
=> 3: after_commit { binding.pry }
4: end
[1] > saved_change_to_name?
=> true
[2] > name_before_last_save
=> "Clément"
[3] > name
=> "Bob"
[4] > previous_changes[:name]
=> ["Clément", "Bob"]
[5] > exit
Here we can see saved_change_to_name? behaves the same in the 2 callbacks after_save and after_commit. It’s very pleasing not to have to ask ourself which helper to use.
Also note previous_changes still exists, and only works in a after_commit callback (with a weird deprecation messages). We would only use this method to fetch a hash of updated attributes with both their previous and current values.
From Rails 5.1, new helper methods are introduced to clarify and simplify usage of dirty attributes inside models callbacks, and replace the old ambiguous ones:
Rails 5.0 Rails 5.1+
<attribute>_changed? → saved_change_to_<attribute>?
<attribute>_was → <attribute>_before_last_save
changed? → saved_changes?
changed_attributes → saved_changes.transform_values(&:first)
Be sure to use them right when upgrading to Rails 5.1. Check your logfiles, as even if it seem to only be a deprecation, new behavior applies.
Reference: