Rails dirty methods

2023-06-29

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.

blog comments powered by Disqus Prev: Custom Postgres Ubuntu Style Next: Survey of SQL:2011 Temporal Features