This project is archived and is in readonly mode.

#2991 ✓committed
Brian Durand

After Transaction Patch

Reported by Brian Durand | August 4th, 2009 @ 03:36 AM | in 3.0.2

This patch provides hooks for callbacks after a transaction rolls back or commits. It is intended to address some inconsistencies with the current Rails code as well as offer hooks for developers to execute code at the appropriate time.

Bug with creating records in transaction block

The current transaction code will roll back a newly created record to the proper state with a nil id and new_record? returning true, but only when an error is encountered while the record is being saved. If you have a multi statement transaction, records will not be rolled back properly. Take this case:

class Model < ActiveRecord::Base
  def after_save
    raise "illegal value" if title == "xxx"

This code works fine:

model = => "xxx")
assert model.new_record?
assert model.new_record?

However, wrap it in a transaction block, and it fails:

Model.transaction do
  model = => "valid")
  assert model.new_record?
  raise ActiveRecord::Rollback
assert model.new_record? # Fail!

This patch solves the issue by keeping track of all records saved or destroyed in a transaction and rolling back the id and new_record? for newly created records when transactions are rolled back.

Callbacks after_commit, after_rollback

In addition, the patch adds callbacks for after_commit and after_rollback which are called after a commit or rollback is sent to the database. The intention is to allow developers to hook in code to communicate with systems external to the database.

For example, a common construct with caching is to add a clear cache call to an after_save callback. However, since after_save callbacks happen within a transaction, the cache will be cleared before the changes is committed to the database. This leaves a small window where another process can regenerate the cache entry:

Process 1 -> begin transaction
Process 1 -> save record
Process 1 -> clear cache
Process 2 -> read cache
Process 2 -> regenerate cache from database
Process 1 -> commit transaction

In this case, Process 2 will generate what will be a stale cache entry out of sync with the database. This bug is most likely to occur in a transaction block or if there are additional after_save callbacks on the model.

As another example, some models may store data external to the database (i.e. on the file system) that needs to be cleaned up if the record is not successfully saved. Once again, this can fail in a transaction block where the record is rolled back after any after save has occurred.

Comments and changes to this ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href=""></a>

Shared Ticket Bins

Referenced by