current_transaction.after_commit is handy

Posted . Visible to the public.

Rails 8 provides a new way of doing things after the current transaction, without defining a model "global" after_commit callback.

def do_something
  update_column(:foo, "bar")
  send_email(foo: "bar")
end

The above code is brittle if it's called within a failing transaction:

ActiveRecord::Base.transaction do
  user.do_something
  raise "oh no, my transaction failed"
end

In the case above, the database update is lost, but the email is sent out. Also, the database transaction is long (this is bad for performance in general) because it waits for send_email, which depending on its implementation makes slow network calls.

You can now rewrite this with current_transaction to without having to define a global after commit callback:

def do_something
  update_column(:foo, "bar")
  ActiveRecord::Base.current_transaction.after_commit { send_email(foo: "bar") }
  ActiveRecord::Base.current_transaction.after_rollback { Rails.logger.warn("rollback") } # after_rollback is also supported
end

If you call current_transaction.after_commit in a nested transaction, it will wait with executing the block until the outermost transaction is committed.

Tip

With ActiveJob and SomeJob.enqueue_after_transaction_commit‎ = true in your jobs, the actual job enqueue will happen after the transaction committed. Rails uses after_commit automatically in this case to enqueue the job after the transaction has committed.

Profile picture of Niklas Hä.
Niklas Hä.
Last edit
Niklas Hä.
License
Source code in this card is licensed under the MIT License.
Posted by Niklas Hä. to makandra dev (2026-07-07 11:44)