Rails has lots of methods to see what attributes have changed on your model. Some tell you the changes you haven’t yet saved; some, the changes you just saved. But the behavior and names of these attributes have changed over time.
I thought I had a handle on this until I saw
saved_change_to_attribute? and wondered how it differs from
attribute_previously_changed?. Turns out they are identical!
Well sort of. The spelling I’m used to,
attribute_previously_changed?, comes from
ActiveModel::Dirty (and is a bit older), whereas
saved_change_to_attribute? is defined in
ActiveRecord::AttributeMethods::Dirty. Not all ActiveModels are ActiveRecords. But in your ActiveRecord classes, they do the same thing.
I’ve linked to Rails 6.1 here. They were nearly identical before that, but for a while one took extra options and the other didn’t. You have to go back to Rails 5.0 to get a more substantial difference, when we had
attribute_previously_changed? but not
saved_change_to_attribute?. They are still identical today in Rails 7. I’m surprised they don’t deprecate the ActiveRecord methods and just use ActiveModel.
Just to give a quick catalog, here is the full set of methods. Anywhere you see
attribute you can replace it with the name of the attribute you care about (which just calls the generic method with its name as parameter).
before you save:
changes changed_attributes # can't replace "attribute" attribute_change attribute_changed? attribute_was changes_to_save has_changes_to_save? attributes_in_database # can't replace "attribute" attribute_in_database changed_attribute_names_to_save # can't replace "attribute" attribute_change_to_be_saved will_save_change_to_attribute?
after you save:
previous_changes attribute_previous_change attribute_previously_changed? attribute_previously_was saved_changes saved_changes? saved_change_to_attribute saved_change_to_attribute? attribute_before_last_save
I’ve grouped the methods from each file, and you can see there are many synonyms.
By the way if you are making heavy use of ActiveRecord callbacks and using these methods to trigger them (e.g.
after_commit :send_shipped_notification if: :shipped_at_previously_changed?), watch out! The conditions on these get evaluated one-by-one, so if some earlier callback saves further changes to the model, your old
previous_changes are lost! The callback you expect to get called just doesn’t. I’ve had to debug that failure way too many times.