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.6.0"
Class Method Summary collapse
-
.after_commit(prepend: false, connection: nil, without_tx: EXECUTE, &callback) ⇒ Object
Runs
callback
after successful commit of outermost transaction for databaseconnection
. -
.after_rollback(prepend: false, connection: nil, &callback) ⇒ Object
Runs
callback
after rolling back of transaction or savepoint (if declared in nested transaction) for databaseconnection
. -
.before_commit(prepend: false, connection: nil, without_tx: WARN_AND_EXECUTE, &callback) ⇒ Object
Runs
callback
before committing of outermost transaction forconnection
. -
.in_transaction(connection = default_connection, requires_new: false, **new_tx_options) ⇒ Object
Makes sure the provided block runs in a transaction.
-
.in_transaction?(connection = nil) ⇒ Boolean
Helper method to determine whether we’re currently in transaction or not.
- .register_callback(prepend:, connection: nil, name:, without_tx:, callback:) ⇒ Object private
Class Method Details
.after_commit(prepend: false, connection: nil, without_tx: EXECUTE, &callback) ⇒ Object
Runs callback
after successful commit of outermost transaction for database connection
.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/after_commit_everywhere.rb', line 41 def after_commit( prepend: false, connection: nil, without_tx: EXECUTE, &callback ) register_callback( prepend: prepend, connection: connection, name: __method__, callback: callback, without_tx: without_tx, ) end |
.after_rollback(prepend: false, connection: nil, &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
97 98 99 100 101 102 103 104 105 |
# File 'lib/after_commit_everywhere.rb', line 97 def after_rollback(prepend: false, connection: nil, &callback) register_callback( prepend: prepend, connection: connection, name: __method__, callback: callback, without_tx: RAISE, ) end |
.before_commit(prepend: false, connection: nil, 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
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/after_commit_everywhere.rb', line 68 def before_commit( prepend: false, connection: nil, without_tx: WARN_AND_EXECUTE, &callback ) if ActiveRecord::VERSION::MAJOR < 5 raise NotImplementedError, "#{__method__} works only with Rails 5.0+" end register_callback( prepend: prepend, connection: connection, name: __method__, callback: callback, without_tx: without_tx, ) end |
.in_transaction(connection = default_connection, requires_new: false, **new_tx_options) ⇒ Object
Makes sure the provided block runs in a transaction. If we are not currently in a transaction, a new transaction is started.
It mimics the ActiveRecord’s transaction
method’s API and actually uses it under the hood.
However, the main difference is that it doesn’t swallow ActiveRecord::Rollback
exception in case when there is no transaction open.
161 162 163 164 165 166 167 |
# File 'lib/after_commit_everywhere.rb', line 161 def in_transaction(connection = default_connection, requires_new: false, **) if in_transaction?(connection) && !requires_new yield else connection.transaction(requires_new: requires_new, **) { yield } end end |
.in_transaction?(connection = nil) ⇒ Boolean
Helper method to determine whether we’re currently in transaction or not
140 141 142 143 144 145 146 147 |
# File 'lib/after_commit_everywhere.rb', line 140 def in_transaction?(connection = nil) # Don't establish new connection if not connected: we apparently not in transaction return false unless connection || ActiveRecord::Base.connection_pool.active_connection? connection ||= default_connection # service transactions (tests and database_cleaner) are not joinable connection.transaction_open? && connection.current_transaction.joinable? end |
.register_callback(prepend:, connection: nil, 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.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/after_commit_everywhere.rb', line 108 def register_callback(prepend:, connection: nil, 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 connection ||= default_connection wrap = Wrap.new(connection: connection, "#{name}": callback) if prepend # Hacking ActiveRecord's transaction internals to prepend our callback # See https://github.com/rails/rails/blob/f0d433bb46ac233ec7fd7fae48f458978908d905/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L148-L156 records = connection.current_transaction.instance_variable_get(:@records) records = connection.current_transaction.instance_variable_set(:@records, []) if records.nil? records.unshift(wrap) else connection.add_transaction_record(wrap) end end |