123456789_123456789_123456789_123456789_123456789_

Module: ActiveRecord::Transactions

Relationships & Source Files
Namespace Children
Modules:
Extension / Inclusion / Inheritance Descendants
Included In:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Defined in: activerecord/lib/active_record/transactions.rb

Overview

See ClassMethods for documentation.

Constant Summary

Class Method Summary

::ActiveSupport::Concern - Extended

class_methods

Define class methods from given block.

included

Evaluate given block in context of base class, so that you can write class macros here.

prepended

Evaluate given block in context of base class, so that you can write class macros here.

append_features, prepend_features

Instance Attribute Summary

Instance Method Summary

DSL Calls

included

[ GitHub ]


9
10
11
12
13
# File 'activerecord/lib/active_record/transactions.rb', line 9

included do
  define_callbacks :commit, :rollback,
                   :before_commit,
                   scope: [:kind, :name]
end

Instance Attribute Details

#_committed_already_called (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 455

attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback

#_last_transaction_return_status (rw)

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 15

attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:

#_new_record_before_last_commit (rw)

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 15

attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:

#_trigger_destroy_callback (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 455

attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback

#_trigger_update_callback (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 455

attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback

#has_transactional_callbacks?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 557

def has_transactional_callbacks?
  !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
end

#trigger_transactional_callbacks?Boolean (readonly)

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 449

def trigger_transactional_callbacks? # :nodoc:
  (@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
    _trigger_destroy_callback && destroyed?
end

Instance Method Details

#add_to_transaction(ensure_finalize = true) (private)

Add the record to the current transaction so that the #after_rollback and #after_commit callbacks can be called.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 551

def add_to_transaction(ensure_finalize = true)
  self.class.with_connection do |connection|
    connection.add_transaction_record(self, ensure_finalize)
  end
end

#before_committed!

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 394

def before_committed! # :nodoc:
  _run_before_commit_callbacks
end

#clear_transaction_record_state (private)

Clear the new record state and id of a record.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 486

def clear_transaction_record_state
  return unless @_start_transaction_state
  @_start_transaction_state[:level] -= 1
  @_start_transaction_state = nil if @_start_transaction_state[:level] < 1
end

#committed!(should_run_callbacks: true)

This method is for internal use only.

Call the #after_commit callbacks.

Ensure that it is not called if the object was never persisted (failed create), but call it after the commit of a destroyed object.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 402

def committed!(should_run_callbacks: true) # :nodoc:
  @_start_transaction_state = nil
  if should_run_callbacks
    @_committed_already_called = true
    _run_commit_callbacks
  end
ensure
  @_committed_already_called = @_trigger_update_callback = @_trigger_destroy_callback = false
end

#destroy

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 378

def destroy # :nodoc:
  with_transaction_returning_status { super }
end

#implicit_persistence_transaction(connection, &block) (private)

::Method called to execute persistence method operations (#save, #destroy, #touch), creating a transaction on the provided connection.

Override this method to customize transaction behavior, for example to set a specific isolation level.

The connection parameter provides access to the current database connection, allowing conditional logic based on connection state (e.g., whether a transaction is already open). The block parameter contains the persistence operation to be executed.

Example skipping transaction creation if one is already open:

class Account < ApplicationRecord
private
  def implicit_persistence_transaction(connection, &block)
    if connection.transaction_open?
      yield
    else
      super
    end
  end
end
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 585

def implicit_persistence_transaction(connection, &block)
  connection.transaction(&block)
end

#init_internals (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 457

def init_internals
  super
  @_start_transaction_state = nil
  @_last_transaction_return_status = nil
  @_committed_already_called = nil
  @_new_record_before_last_commit = nil
end

#remember_transaction_record_state (private)

Save the new record state and id of a record so it can be restored later if a transaction fails.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 466

def remember_transaction_record_state
  @_start_transaction_state ||= {
    id: id,
    new_record: @new_record,
    previously_new_record: @previously_new_record,
    destroyed: @destroyed,
    attributes: @attributes,
    frozen?: frozen?,
    level: 0
  }
  @_start_transaction_state[:level] += 1

  if _committed_already_called
    @_new_record_before_last_commit = false
  else
    @_new_record_before_last_commit = @_start_transaction_state[:new_record]
  end
end

#restore_transaction_record_state(force_restore_state = false) (private)

Restore the new record state and id of a record that was previously saved by a call to save_record_state.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 493

def restore_transaction_record_state(force_restore_state = false)
  if restore_state = @_start_transaction_state
    if force_restore_state || restore_state[:level] <= 1
      @new_record = restore_state[:new_record]
      @previously_new_record = restore_state[:previously_new_record]
      @destroyed = restore_state[:destroyed]
      locking_column = self.class.locking_column if self.class.locking_enabled?
      @attributes = restore_state[:attributes].map do |attr|
        if attr.name == locking_column
          # The locking column is bumped by `_update_row` itself, not the caller, and
          # `_update_row` writes the new value into the same `@attributes` object that
          # the snapshot is holding a reference to (because the snapshot wraps the
          # `AttributeSet` rather than deep-duping it). After a successful save,
          # `forget_attribute_assignments` reassigns `@attributes`, so subsequent
          # operations see a clean attribute, but the snapshot retains the dirty one.
          # Forcibly rebuild the locking column attribute from its (still-correct)
          # original value so the next save uses the pristine value in the WHERE
          # clause and doesn't raise `StaleObjectError` after a rollback.
          next attr.with_value_from_database(attr.original_value)
        end
        value = @attributes.fetch_value(attr.name)
        attr = attr.with_value_from_user(value) if attr.value != value
        attr
      end
      @mutations_from_database = nil
      @mutations_before_last_save = nil
      if self.class.composite_primary_key?
        if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) }
          @primary_key.zip(restore_state[:id]).each do |col, val|
            @attributes.write_from_user(col, val)
          end
        end
      else
        if @attributes.fetch_value(@primary_key) != restore_state[:id]
          @attributes.write_from_user(@primary_key, restore_state[:id])
        end
      end
      freeze if restore_state[:frozen?]
    end
  end
end

#rolledback!(force_restore_state: false, should_run_callbacks: true)

This method is for internal use only.

Call the #after_rollback callbacks. The force_restore_state argument indicates if the record state should be rolled back to the beginning or just to the last savepoint.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 414

def rolledback!(force_restore_state: false, should_run_callbacks: true) # :nodoc:
  if should_run_callbacks
    _run_rollback_callbacks
  end
ensure
  restore_transaction_record_state(force_restore_state)
  clear_transaction_record_state
  @_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
end

#save

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 382

def save(**) # :nodoc:
  with_transaction_returning_status { super }
end

#save!

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 386

def save!(**) # :nodoc:
  with_transaction_returning_status { super }
end

#touch

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 390

def touch(*, **) # :nodoc:
  with_transaction_returning_status { super }
end

#transaction(**options, &block)

See Transactions::ClassMethods for detailed documentation.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 374

def transaction(**options, &block)
  self.class.transaction(**options, &block)
end

#transaction_include_any_action?(actions) ⇒ Boolean (private)

Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 536

def transaction_include_any_action?(actions)
  actions.any? do |action|
    case action
    when :create
      persisted? && @_new_record_before_last_commit
    when :update
      !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
    when :destroy
      _trigger_destroy_callback
    end
  end
end

#with_transaction_returning_status

This method is for internal use only.

Executes a block within a transaction and captures its return value as a status flag. If the status is true, the transaction is committed, otherwise a ROLLBACK is issued. In any case, the status flag is returned.

This method is available within the context of an Base instance.

[ GitHub ]

  
# File 'activerecord/lib/active_record/transactions.rb', line 430

def with_transaction_returning_status # :nodoc:
  self.class.with_connection do |connection|
    connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
      status = nil
      ensure_finalize = !connection.transaction_open?

      implicit_persistence_transaction(connection) do
        add_to_transaction(ensure_finalize || has_transactional_callbacks?)
        remember_transaction_record_state

        status = yield
        raise ActiveRecord::Rollback unless status
      end
      @_last_transaction_return_status = status
      status
    end
  end
end