Need to check if a block of attributes has changed before update in Rails 3.
street1, street2, city, state, zipcode
I know I could use something like
if @user.street1 != params[:user][:street1]
then do something....
end
But that piece of code will be REALLY long. Is there a cleaner way?
Check out ActiveModel::Dirty (available on all models by default). The documentation is really good, but it lets you do things such as:
@user.street1_changed? # => true/false
This is how I solved the problem of checking for changes in multiple attributes.
attrs = ["street1", "street2", "city", "state", "zipcode"]
if (@user.changed & attrs).any?
then do something....
end
The changed
method returns an array of the attributes changed for that object.
Both @user.changed
and attrs
are arrays so I can get the intersection (see ary & other ary
method). The result of the intersection is an array. By calling any?
on the array, I get true if there is at least one intersection.
Also very useful, the changed_attributes
method returns a hash of the attributes with their original values and the changes
returns a hash of the attributes with their original and new values (in an array).
You can check APIDock for which versions supported these methods.
http://apidock.com/rails/ActiveModel/Dirty
ActiveModel::Dirty
didn't work for me because the @model.update_attributes()
hid the changes. So this is how I detected changes it in an update
method in a controller:
def update
@model = Model.find(params[:id])
detect_changes
if @model.update_attributes(params[:model])
do_stuff if attr_changed?
end
end
private
def detect_changes
@changed = []
@changed << :attr if @model.attr != params[:model][:attr]
end
def attr_changed?
@changed.include :attr
end
If you're trying to detect a lot of attribute changes it could get messy though. Probably shouldn't do this in a controller, but meh.
Above answers are better but yet for knowledge we have another approch as well,
Lets 'catagory' column value changed for an object (@design),
@design.changes.has_key?('catagory')
The .changes will return a hash with key as column's name and values as a array with two values [old_value, new_value] for each columns. For example catagory for above is changed from 'ABC' to 'XYZ' of @design,
@design.changes # => {}
@design.catagory = 'XYZ'
@design.changes # => { 'catagory' => ['ABC', 'XYZ'] }
For references change in ROR
For rails 5.1+ callbacks
To improve this post and complete other answers
The first answer will be deprecated for callbacks at next rails versions (after 5.1).
So the new way to check if an attribute has changed is with saved_change_to_attribute?
You can do:
@user.saved_change_to_street1? # => true/false
You can see more examples here at this post blog