Module: AfterCommitEverywhere

Defined in:
lib/after_commit_everywhere.rb,
lib/after_commit_everywhere/wrap.rb,
lib/after_commit_everywhere/version.rb

Overview

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)

Defined Under Namespace

Classes: NotInTransaction, Wrap

Constant Summary collapse

VERSION =
"1.1.0"

Class Method Summary collapse

Class Method Details

.after_commit(connection: ActiveRecord::Base.connection, &callback) ⇒ Object

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

If called outside transaction it will execute callback immediately.

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter) (defaults to: ActiveRecord::Base.connection)
  • callback (#call)

    Callback to be executed

Returns:

  • void



28
29
30
31
32
33
34
35
# 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) ⇒ Object

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

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter) (defaults to: ActiveRecord::Base.connection)
  • callback (#call)

    Callback to be executed

Returns:

  • void

Raises:



69
70
71
72
73
74
75
76
# 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) ⇒ Object

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

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter) (defaults to: ActiveRecord::Base.connection)
  • callback (#call)

    Callback to be executed

Returns:

  • void



46
47
48
49
50
51
52
53
54
55
56
57
# 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) ⇒ Boolean

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

Returns:

  • (Boolean)


98
99
100
101
# 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:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 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