[ruby-on-rails] Rails 3 check if attribute changed

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?

This question is related to ruby-on-rails ruby-on-rails-3

The answer is


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.


For rails 5.1+ callbacks

As of Ruby on Rails 5.1, the attribute_changed? and attribute_was ActiveRecord methods will be deprecated

Use saved_change_to_attribute? instead of attribute_changed?

@user.saved_change_to_street1? # => true/false

More examples here


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