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

RAISE =

Causes before_commit and after_commit to raise an exception when called outside a transaction.

:raise
EXECUTE =

Causes before_commit and after_commit to execute the given callback immediately when called outside a transaction.

:execute
WARN_AND_EXECUTE =

Causes before_commit and after_commit to log a warning before calling the given callback immediately when called outside a transaction.

:warn_and_execute
VERSION =
"1.2.0"

Class Method Summary collapse

Class Method Details

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

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

Parameters:

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter) (defaults to: ActiveRecord::Base.connection)
  • without_tx (Symbol) (defaults to: EXECUTE)

    Determines the behavior of this function when called without an open transaction.

    Must be one of: RAISE, EXECUTE, or WARN_AND_EXECUTE.

  • callback (#call)

    Callback to be executed

Returns:

  • void



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/after_commit_everywhere.rb', line 41

def after_commit(
  connection: ActiveRecord::Base.connection,
  without_tx: EXECUTE,
  &callback
)
  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    without_tx: without_tx,
  )
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:



93
94
95
96
97
98
99
100
# File 'lib/after_commit_everywhere.rb', line 93

def after_rollback(connection: ActiveRecord::Base.connection, &callback)
  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    without_tx: RAISE,
  )
end

.before_commit(connection: ActiveRecord::Base.connection, without_tx: WARN_AND_EXECUTE, &callback) ⇒ Object

Runs callback before committing of outermost transaction for connection.

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)
  • without_tx (Symbol) (defaults to: WARN_AND_EXECUTE)

    Determines the behavior of this function when called without an open transaction.

    Must be one of: RAISE, EXECUTE, or WARN_AND_EXECUTE.

  • callback (#call)

    Callback to be executed

Returns:

  • void



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/after_commit_everywhere.rb', line 66

def before_commit(
  connection: ActiveRecord::Base.connection,
  without_tx: WARN_AND_EXECUTE,
  &callback
)
  if ActiveRecord::VERSION::MAJOR < 5
    raise NotImplementedError, "#{__method__} works only with Rails 5.0+"
  end

  register_callback(
    connection: connection,
    name: __method__,
    callback: callback,
    without_tx: without_tx,
  )
end

.in_transaction?(connection = ActiveRecord::Base.connection) ⇒ Boolean

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

Returns:

  • (Boolean)


124
125
126
127
# File 'lib/after_commit_everywhere.rb', line 124

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:, without_tx:, 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)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/after_commit_everywhere.rb', line 103

def register_callback(connection:, name:, without_tx:, callback:)
  raise ArgumentError, "Provide callback to #{name}" unless callback

  unless in_transaction?(connection)
    case without_tx
    when WARN_AND_EXECUTE
      warn "#{name}: No transaction open. Executing callback immediately."
      return callback.call
    when EXECUTE
      return callback.call
    when RAISE
      raise NotInTransaction, "#{name} is useless outside transaction"
    else
      raise ArgumentError, "Invalid \"without_tx\": \"#{without_tx}\""
    end
  end
  wrap = Wrap.new(connection: connection, "#{name}": callback)
  connection.add_transaction_record(wrap)
end