Skip to content

Instantly share code, notes, and snippets.

@trantuanckc
Forked from kieetnvt/rails_5_ar_dirty_sp.md
Created October 9, 2020 15:10
Show Gist options
  • Save trantuanckc/2f89f1537536b3c7bb08d924076f7ad7 to your computer and use it in GitHub Desktop.
Save trantuanckc/2f89f1537536b3c7bb08d924076f7ad7 to your computer and use it in GitHub Desktop.
Rails > 5 AR Dirty

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.

TO SUMMARIZE

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment