I am using an after_commit in my application.
I would like it to trigger only when a particular field is updated in my model. Anyone know how to do that?
I am using an after_commit in my application.
I would like it to trigger only when a particular field is updated in my model. Anyone know how to do that?
 
    
    Old question, but this is one method that I've found that might work with the after_commit callback (working off paukul's answer). At least, the values both persist post-commit in IRB.
after_commit :callback, 
  if: proc { |record| 
    record.previous_changes.key?(:attribute) &&
      record.previous_changes[:attribute].first != record.previous_changes[:attribute].last
  }
 
    
    Answering this old question because it still pops up in search results
you can use the previous_changes method which returnes a hash of the format:
{ "changed_attribute" => ["old value", "new value"] }
it's what changes was until the record gets actually saved (from active_record/attribute_methods/dirty.rb):
  def save(*) #:nodoc:
    if status = super
      @previously_changed = changes
      @changed_attributes.clear
      # .... whatever goes here
so in your case you can check for previous_changes.key? "your_attribute" or something like that
 
    
    Old question but still pops up in search results.
As of Rails 5 attribute_changed? was deprecated. Using saved_change_to_attribute? instead of attribute_changed? is recommended.
 
    
     
    
    This is a very old problem, but the accepted previous_changes solution just isn't robust enough. In an ActiveRecord transaction, there are many reasons why you might save a Model twice. previous_changes only reflects the result of the final save.  Consider this example
class Test < ActiveRecord::Base
  after_commit: :after_commit_test
  def :after_commit_test
    puts previous_changes.inspect
  end
end
test = Test.create(number: 1, title: "1")
test = Test.find(test.id) # to initialize a fresh object
test.transaction do
  test.update(number: 2)
  test.update(title: "2")
end
which outputs:
{"title"=>["1", "2"], "updated_at"=>[...]}
but, what you need is:
{"title"=>["1", "2"], "number"=>[1, 2], "updated_at"=>[...]}
So, my solution is this:
module TrackSavedChanges
  extend ActiveSupport::Concern
  included do
    # expose the details if consumer wants to do more
    attr_reader :saved_changes_history, :saved_changes_unfiltered
    after_initialize :reset_saved_changes
    after_save :track_saved_changes
  end
  # on initalize, but useful for fine grain control
  def reset_saved_changes
    @saved_changes_unfiltered = {}
    @saved_changes_history = []
  end
  # filter out any changes that result in the original value
  def saved_changes
    @saved_changes_unfiltered.reject { |k,v| v[0] == v[1] }
  end
  private
  # on save
  def track_saved_changes
    # maintain an array of ActiveModel::Dirty.changes
    @saved_changes_history << changes.dup
    # accumulate the most recent changes
    @saved_changes_history.last.each_pair { |k, v| track_saved_change k, v }
  end
  # v is an an array of [prev, current]
  def track_saved_change(k, v)
    if @saved_changes_unfiltered.key? k
      @saved_changes_unfiltered[k][1] = track_saved_value v[1]
    else
      @saved_changes_unfiltered[k] = v.dup
    end
  end
  # type safe dup inspred by http://stackoverflow.com/a/20955038
  def track_saved_value(v)
    begin
      v.dup
    rescue TypeError
      v
    end
  end
end
which you can try out here: https://github.com/ccmcbeck/after-commit
 
    
    I don't think you can do it in after_commit
The after_commit is called after the transaction is commited Rails Transactions
For example in my rails console
> record = MyModel.find(1)
=> #<MyModel id: 1, label: "test", created_at: "2011-08-19 22:57:54", updated_at: "2011-08-19 22:57:54">
> record.label = "Changing text"
=> "Changing text"
> record.label_changed?
=> true
> record.save
=> true
> record.label_changed?
=> false 
Therefore you won't be able to use the :if condition on after_commit because the attribute will not be marked as changed anymore as it has been saved. You may need to track whether the field you are after is changed? in another callback before the record is saved?
 
    
    It sounds like you want something like a conditional callback. If you had posted some code I could have pointed you in the right direction however I think you would want to use something like this:
after_commit :callback,
  :if => Proc.new { |record| record.field_modified? }
 
    
    Use gem ArTransactionChanges. previous_changes is not working for me in Rails 4.0.x
Usage:
class User < ActiveRecord::Base
  include ArTransactionChanges
  after_commit :print_transaction_changes
  def print_transaction_changes
    transaction_changed_attributes.each do |name, old_value|
      puts "attribute #{name}: #{old_value.inspect} -> #{send(name).inspect}"
    end
  end
end
