Callbacks outside of the database transaction

by: Michael Raidel | posted: September 1st, 2010

Callbacks are a fantastic way to trigger behaviour at a specific time in the lifecycle of an ActiveRecord object. If for example you want to do something before every save you would use the before_save callback. All of these callbacks run inside of a transaction which ensures data integrity. If something goes wrong, everything is rolled back.

The problem

But there are some cases where you want to run the callback after the transaction is committed. One example are expensive operations like image thumbnailing which would block the transaction too long. Another example is asynchronous processing in a queue. In an application which is using resque as the queueing system I just had the problem that every once in a while a job failed because the ActiveRecord object could not be found. The reason for this was the transaction: the resque job was queued in an after_create callback and in some rare cases the resque worker started the job faster than the database could finish the transaction! This could also happen when updating a search index or in any other task which depends on the updated state of the database.

The solution

In Rails 3 we can use the after_commit callbacks, which are triggered right after the commit of the transaction.

  # triggers after the commit of every save
  after_commit :method_name
  # triggers after the commit of a create
  after_commit :method_name, :on => :create
  # triggers after the commit of an update
  after_commit :method_name, :on => :update
  # triggers after the commit of a destroy
  after_commit :method_name, :on => :destroy

When using Rails 2

As the application I am working on is still on Rails 2.3 I am now using the after_commit gem which provides this functionality with a slightly different syntax also for earlier versions of Rails (for all versions between 1.2 and 2.3).

  # triggers after the commit of every save
  after_commit :method_name
  # triggers after the commit of a create
  after_commit_on_create :method_name
  # triggers after the commit of an update
  after_commit_on_update :method_name
  # triggers after the commit of a destroy
  after_commit_on_destroy :method_name

Callbacks for rollback

Both the after_commit gem and Rails 3 are also providing additional callbacks which trigger on a rollback of the transaction. These callbacks could be very useful for cleaning up things outside of the database.

  after_rollback :method_name