How to detect attribute changes from model?

Ruby on-Rails

Ruby on-Rails Problem Overview


I'd like to create a callback function in rails that executes after a model is saved.

I have this model, Claim that has a attribute 'status' which changes depending on the state of the claim, possible values are pending, endorsed, approved, rejected

The database has 'state' with the default value of 'pending'.

I'd like to perform certain tasks after the model is created on the first time or updated from one state to another, depending on which state it changes from.

My idea is to have a function in the model:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this
    
      # if status changed from pending to approved
      performthistask
     end

My question is how do I check for the previous value before the change within the model?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

You should look at [ActiveModel::Dirty][1] module: You should be able to perform following actions on your Claim model:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

Oh! the joys of Rails! [1]: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

Solution 2 - Ruby on-Rails

you can use this

self.changed

it return an array of all columns that changed in this record

you can also use

self.changes

which returns a hash of columns that changed and before and after results as arrays

Solution 3 - Ruby on-Rails

For Rails 5.1+, you should use active record attribute method: saved_change_to_attribute?

> saved_change_to_attribute?(attr_name, **options)> > Did this attribute change when we last saved? This method can be > invoked assaved_change_to_name?instead of >saved_change_to_attribute?("name"). Behaves similarly to > attribute_changed?. This method is useful in after callbacks to > determine if the call to save changed a certain attribute. > > **Options** > > fromWhen passed, this method will return false unless the original > value is equal to the given option > >to` When passed, this method will return false unless the value was > changed to the given value

So your model will look like this, if you want to call some method based on the change in attribute value:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

And if you don't want to check for value change in callback, you can do the following::

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end

Solution 4 - Ruby on-Rails

I recommend you have a look at one of the available state machine plugins:

Either one will let you setup states and transitions between states. Very useful and easy way of handling your requirements.

Solution 5 - Ruby on-Rails

I've seen the question rise in many places, so I wrote a tiny rubygem for it, to make the code a little nicer (and avoid a million if/else statements everywhere): https://github.com/ronna-s/on_change. I hope that helps.

Solution 6 - Ruby on-Rails

You will be much better off using a well tested solution such as the state_machine gem.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid CView Question on Stackoverflow
Solution 1 - Ruby on-RailsHarish ShettyView Answer on Stackoverflow
Solution 2 - Ruby on-RailszeacussView Answer on Stackoverflow
Solution 3 - Ruby on-RailsRajkaran MishraView Answer on Stackoverflow
Solution 4 - Ruby on-RailsToby HedeView Answer on Stackoverflow
Solution 5 - Ruby on-RailsRonnaView Answer on Stackoverflow
Solution 6 - Ruby on-RailsPaz ArichaView Answer on Stackoverflow