Class: ActiveRecord::Transaction
Relationships & Source Files | |
Inherits: | Object |
Defined in: | activerecord/lib/active_record/transaction.rb |
Overview
::Class
specifies the interface to interact with the current transaction state.
It can either map to an actual transaction/savepoint, or represent the absence of a transaction.
State
We say that a transaction is finalized when it wraps a real transaction that has been either committed or rolled back.
A transaction is open if it wraps a real transaction that is not finalized.
On the other hand, a transaction is closed when it is not open. That is, when it represents absence of transaction, or it wraps a real but finalized one.
You can check whether a transaction is open or closed with the #open? and #closed? predicates:
if Article.current_transaction.open?
# We are inside a real and not finalized transaction.
end
Closed transactions are #blank? too.
Callbacks
After updating the database state, you may sometimes need to perform some extra work, or reflect these changes in a remote system like clearing or updating a cache:
def publish_article(article)
article.update!(published: true)
NotificationService.article_published(article)
end
The above code works but has one important flaw, which is that it no longer works properly if called inside a transaction, as it will interact with the remote system before the changes are persisted:
Article.transaction do
article = create_article(article)
publish_article(article)
end
The callbacks offered by Transaction
allow to rewriting this method in a way that is compatible with transactions:
def publish_article(article)
article.update!(published: true)
Article.current_transaction.after_commit do
NotificationService.article_published(article)
end
end
In the above example, if publish_article
is called inside a transaction, the callback will be invoked after the transaction is successfully committed, and if called outside a transaction, the callback will be invoked immediately.
Caveats
When using after_commit callbacks, it is important to note that if the callback raises an error, the transaction won’t be rolled back as it was already committed. Relying solely on these to synchronize state between multiple systems may lead to consistency issues.
Constant Summary
-
NULL_TRANSACTION =
# File 'activerecord/lib/active_record/transaction.rb', line 130new(nil).freeze
Class Method Summary
- .new(internal_transaction) ⇒ Transaction constructor Internal use only
Instance Attribute Summary
-
#blank?
readonly
Alias for #closed?.
-
#closed? ⇒ Boolean
(also: #blank?)
readonly
Returns true if the transaction doesn’t exist or is finalized.
-
#open? ⇒ Boolean
readonly
Returns true if the transaction exists and isn’t finalized yet.
Instance Method Summary
-
#after_commit(&block)
Registers a block to be called after the transaction is fully committed.
-
#after_rollback(&block)
Registers a block to be called after the transaction is rolled back.
-
#uuid
Returns a UUID for this transaction or
nil
if no transaction is open.
Constructor Details
.new(internal_transaction) ⇒ Transaction
# File 'activerecord/lib/active_record/transaction.rb', line 69
def initialize(internal_transaction) # :nodoc: @internal_transaction = internal_transaction @uuid = nil end
Instance Attribute Details
#blank? (readonly)
Alias for #closed?.
# File 'activerecord/lib/active_record/transaction.rb', line 121
alias_method :blank?, :closed?
#closed? ⇒ Boolean
(readonly)
Also known as: #blank?
Returns true if the transaction doesn’t exist or is finalized.
# File 'activerecord/lib/active_record/transaction.rb', line 117
def closed? @internal_transaction.nil? || @internal_transaction.state.finalized? end
#open? ⇒ Boolean
(readonly)
Returns true if the transaction exists and isn’t finalized yet.
# File 'activerecord/lib/active_record/transaction.rb', line 112
def open? !closed? end
Instance Method Details
#after_commit(&block)
Registers a block to be called after the transaction is fully committed.
If there is no currently open transactions, the block is called immediately, unless the transaction is finalized, in which case attempting to register the callback raises ActiveRecordError
.
If the transaction has a parent transaction, the callback is transferred to the parent when the current transaction commits, or dropped when the current transaction is rolled back. This operation is repeated until the outermost transaction is reached.
If the callback raises an error, the transaction remains committed.
# File 'activerecord/lib/active_record/transaction.rb', line 85
def after_commit(&block) if @internal_transaction.nil? yield else @internal_transaction.after_commit(&block) end end
#after_rollback(&block)
Registers a block to be called after the transaction is rolled back.
If there is no currently open transactions, the block is not called. But if the transaction is finalized, attempting to register the callback raises ActiveRecordError
.
If the transaction is successfully committed but has a parent transaction, the callback is automatically added to the parent transaction.
If the entire chain of nested transactions are all successfully committed, the block is never called.
If the transaction is already finalized, attempting to register a callback will raise ActiveRecordError
.
# File 'activerecord/lib/active_record/transaction.rb', line 107
def after_rollback(&block) @internal_transaction&.after_rollback(&block) end
#uuid
Returns a UUID for this transaction or nil
if no transaction is open.