mirror of https://github.com/rails/rails
Clarify `current_transaction` behavior.
The documentation wasn't making it clear that a `NullTransaction` is returned when no transaction is active. While we're not going to document `NullTransaction` itself, we can more explictly explain that `current_transaction` always returns an object that responds to the `ActiveRecord::Transaction` interface.
This commit is contained in:
parent
8336aa7f0b
commit
e104356c73
|
@ -109,6 +109,7 @@ module ActiveRecord
|
|||
def initialize; end
|
||||
def state; end
|
||||
def closed?; true; end
|
||||
alias_method :blank?, :closed?
|
||||
def open?; false; end
|
||||
def joinable?; false; end
|
||||
def add_record(record, _ = true); end
|
||||
|
@ -272,8 +273,6 @@ module ActiveRecord
|
|||
|
||||
def full_rollback?; true; end
|
||||
def joinable?; @joinable; end
|
||||
def closed?; false; end
|
||||
def open?; !closed?; end
|
||||
|
||||
private
|
||||
def unique_records
|
||||
|
|
|
@ -3,6 +3,46 @@
|
|||
require "active_support/core_ext/digest"
|
||||
|
||||
module ActiveRecord
|
||||
# This abstract class specifies the interface to interact with the current transaction state.
|
||||
#
|
||||
# Any other methods not specified here are considered to be private interfaces.
|
||||
#
|
||||
# == 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 ActiveRecord::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. Relying solely on these to synchronize state between multiple systems may lead to consistency issues.
|
||||
class Transaction
|
||||
class Callback # :nodoc:
|
||||
def initialize(event, callback)
|
||||
|
@ -35,6 +75,8 @@ module ActiveRecord
|
|||
# If the current 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 is rolled back.
|
||||
def before_commit(&block)
|
||||
(@callbacks ||= []) << Callback.new(:before_commit, block)
|
||||
end
|
||||
|
@ -46,6 +88,8 @@ module ActiveRecord
|
|||
# If the current 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.
|
||||
def after_commit(&block)
|
||||
(@callbacks ||= []) << Callback.new(:after_commit, block)
|
||||
end
|
||||
|
@ -63,13 +107,24 @@ module ActiveRecord
|
|||
(@callbacks ||= []) << Callback.new(:after_rollback, block)
|
||||
end
|
||||
|
||||
# Returns true if a transaction was started.
|
||||
def open?
|
||||
true
|
||||
end
|
||||
alias_method :blank?, :open?
|
||||
|
||||
# Returns true if no transaction is currently active.
|
||||
def closed?
|
||||
false
|
||||
end
|
||||
|
||||
# Returns a UUID for this transaction.
|
||||
def uuid
|
||||
@uuid ||= Digest::UUID.uuid_v4
|
||||
end
|
||||
|
||||
protected
|
||||
def append_callbacks(callbacks)
|
||||
def append_callbacks(callbacks) # :nodoc:
|
||||
(@callbacks ||= []).concat(callbacks)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -235,7 +235,13 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the current transaction. See ActiveRecord::Transactions API docs.
|
||||
# Returns a representation of the current transaction state,
|
||||
# which can be a top level transaction, a savepoint, or the absence of a transaction.
|
||||
#
|
||||
# An object is always returned, whether or not a transaction is currently active.
|
||||
# To check if a transaction was opened, use <tt>current_transaction.open?</tt>.
|
||||
#
|
||||
# See the ActiveRecord::Transaction documentation for detailed behavior.
|
||||
def current_transaction
|
||||
connection_pool.active_connection&.current_transaction || ConnectionAdapters::TransactionManager::NULL_TRANSACTION
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue