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 = truein your jobs, the actual job enqueue will happen after the transaction committed. Rails usesafter_commitautomatically in this case to enqueue the job after the transaction has committed.