class ActiveRecord::ConnectionAdapters::Transaction
:nodoc:
def add_record(record, ensure_finalize = true)
def add_record(record, ensure_finalize = true) @records ||= [] if ensure_finalize @records << record else @lazy_enrollment_records ||= ObjectSpace::WeakMap.new @lazy_enrollment_records[record] = record end end
def after_commit(&block)
def after_commit(&block) if @state.finalized? raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" end (@callbacks ||= []) << Callback.new(:after_commit, block) end
def after_rollback(&block)
def after_rollback(&block) if @state.finalized? raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" end (@callbacks ||= []) << Callback.new(:after_rollback, block) end
def append_callbacks(callbacks) # :nodoc:
def append_callbacks(callbacks) # :nodoc: (@callbacks ||= []).concat(callbacks) end
def before_commit(&block)
def before_commit(&block) if @state.finalized? raise ActiveRecordError, "Cannot register callbacks on a finalized transaction" end (@callbacks ||= []) << Callback.new(:before_commit, block) end
def before_commit_records
def before_commit_records if @run_commit_callbacks if records if ActiveRecord.before_committed_on_all_records ite = unique_records instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates| candidates[record] = record end run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| record.before_committed! if should_run_callbacks end else records.uniq.each(&:before_committed!) end end @callbacks&.each(&:before_commit) end # Note: When @run_commit_callbacks is false #commit_records takes care of appending # remaining callbacks to the parent transaction end
def closed?
def closed? false end
def commit_records
def commit_records if records begin ite = unique_records if @run_commit_callbacks instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite) run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| record.committed!(should_run_callbacks: should_run_callbacks) end else while record = ite.shift # if not running callbacks, only adds the record to the parent transaction connection.add_transaction_record(record) end end ensure ite&.each { |i| i.committed!(should_run_callbacks: false) } end end if @run_commit_callbacks @callbacks&.each(&:after_commit) elsif @callbacks connection.current_transaction.append_callbacks(@callbacks) end end
def dirty!
def dirty! @dirty = true end
def dirty?
def dirty? @dirty end
def full_rollback?; true; end
def full_rollback?; true; end
def incomplete!
def incomplete! @instrumenter.finish(:incomplete) if materialized? end
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false) super() @connection = connection @state = TransactionState.new @callbacks = nil @records = nil @isolation_level = isolation @materialized = false @joinable = joinable @run_commit_callbacks = run_commit_callbacks @lazy_enrollment_records = nil @dirty = false @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction) end
def joinable?; @joinable; end
def joinable?; @joinable; end
def materialize!
def materialize! @materialized = true @instrumenter.start end
def materialized?
def materialized? @materialized end
def open?
def open? true end
def prepare_instances_to_run_callbacks_on(records)
def prepare_instances_to_run_callbacks_on(records) records.each_with_object({}) do |record, candidates| next unless record.trigger_transactional_callbacks? earlier_saved_candidate = candidates[record] next if earlier_saved_candidate && record.class.run_commit_callbacks_on_first_saved_instances_in_transaction # If the candidate instance destroyed itself in the database, then # instances which were added to the transaction afterwards, and which # think they updated themselves, are wrong. They should not replace # our candidate as an instance to run callbacks on next if earlier_saved_candidate&.destroyed? && !record.destroyed? # If the candidate instance was created inside of this transaction, # then instances which were subsequently loaded from the database # and updated need that state transferred to them so that # the after_create_commit callbacks are run record._new_record_before_last_commit = true if earlier_saved_candidate&._new_record_before_last_commit # The last instance to save itself is likeliest to have internal # state that matches what's committed to the database candidates[record] = record end end
def records
def records if @lazy_enrollment_records @records.concat @lazy_enrollment_records.values @lazy_enrollment_records = nil end @records end
def restartable?
Can this transaction's current state be recreated by
def restartable? joinable? && !dirty? end
def restore!
def restore! if materialized? incomplete! @materialized = false materialize! end end
def rollback_records
def rollback_records if records begin ite = unique_records instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite) run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks| record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks) end ensure ite&.each do |i| i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) end end end @callbacks&.each(&:after_rollback) end
def run_action_on_records(records, instances_to_run_callbacks_on)
def run_action_on_records(records, instances_to_run_callbacks_on) while record = records.shift should_run_callbacks = record.__id__ == instances_to_run_callbacks_on[record].__id__ yield record, should_run_callbacks end end
def unique_records
def unique_records records.uniq(&:__id__) end