module AfterCommitEverywhere

Module allowing to use ActiveRecord transactional callbacks outside of ActiveRecord models, literally everywhere in your application.

Include it to your classes (e.g. your base service object class or whatever)

Constants

VERSION

Public Class Methods

after_commit(connection: ActiveRecord::Base.connection, &callback) click to toggle source

Runs callback after successful commit of outermost transaction for database connection.

If called outside transaction it will execute callback immediately.

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param callback [#call] Callback to be executed @return void

# File lib/after_commit_everywhere.rb, line 28
def after_commit(connection: ActiveRecord::Base.connection, &callback)
  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    no_tx_action: :execute,
  )
end
after_rollback(connection: ActiveRecord::Base.connection, &callback) click to toggle source

Runs callback after rolling back of transaction or savepoint (if declared in nested transaction) for database connection.

Caveat: do not raise ActivRecord::Rollback in nested transaction block! See api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#module-ActiveRecord::Transactions::ClassMethods-label-Nested+transactions

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param callback [#call] Callback to be executed @return void @raise [NotInTransaction] if called outside transaction.

# File lib/after_commit_everywhere.rb, line 69
def after_rollback(connection: ActiveRecord::Base.connection, &callback)
  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    no_tx_action: :exception,
  )
end
before_commit(connection: ActiveRecord::Base.connection, &callback) click to toggle source

Runs callback before committing of outermost transaction for connection.

If called outside transaction it will execute callback immediately.

Available only since Ruby on Rails 5.0. See github.com/rails/rails/pull/18936

@param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] @param callback [#call] Callback to be executed @return void

# File lib/after_commit_everywhere.rb, line 46
def before_commit(connection: ActiveRecord::Base.connection, &callback)
  if ActiveRecord::VERSION::MAJOR < 5
    raise NotImplementedError, "#{__method__} works only with Rails 5.0+"
  end

  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    no_tx_action: :warn_and_execute,
  )
end
in_transaction?(connection = ActiveRecord::Base.connection) click to toggle source

Helper method to determine whether we're currently in transaction or not

# File lib/after_commit_everywhere.rb, line 98
def in_transaction?(connection = ActiveRecord::Base.connection)
  # service transactions (tests and database_cleaner) are not joinable
  connection.transaction_open? && connection.current_transaction.joinable?
end
register_callback(connection:, name:, no_tx_action:, callback:) click to toggle source

@api private

# File lib/after_commit_everywhere.rb, line 79
def register_callback(connection:, name:, no_tx_action:, callback:)
  raise ArgumentError, "Provide callback to #{name}" unless callback

  unless in_transaction?(connection)
    case no_tx_action
    when :warn_and_execute
      warn "#{name}: No transaction open. Executing callback immediately."
      return callback.call
    when :execute
      return callback.call
    when :exception
      raise NotInTransaction, "#{name} is useless outside transaction"
    end
  end
  wrap = Wrap.new(connection: connection, "#{name}": callback)
  connection.add_transaction_record(wrap)
end