diff --git a/README.md b/README.md index 0e537c4..d34b73a 100644 --- a/README.md +++ b/README.md @@ -682,6 +682,14 @@ This feature can be disabled conditionally: Journaled.transactional_batching_enabled = false ``` +And can then be enabled via the following block: + +```ruby +Journaled.with_transactional_batching do + # your code +end +``` + Backwards compatibility has been included for background jobs enqueued by version 4.0 and above, but **has been dropped for jobs emitted by versions prior to 4.0**. (Again, be sure to upgrade only one major version at a time.) diff --git a/lib/journaled.rb b/lib/journaled.rb index 9fa3f83..772ef9a 100644 --- a/lib/journaled.rb +++ b/lib/journaled.rb @@ -16,7 +16,19 @@ module Journaled mattr_accessor(:http_open_timeout) { 2 } mattr_accessor(:http_read_timeout) { 60 } mattr_accessor(:job_base_class_name) { 'ActiveJob::Base' } - mattr_accessor(:transactional_batching_enabled) { true } + mattr_writer(:transactional_batching_enabled) { true } + + def self.transactional_batching_enabled? + Thread.current[:journaled_transactional_batching_enabled] || @@transactional_batching_enabled + end + + def self.with_transactional_batching + value_was = Thread.current[:journaled_transactional_batching_enabled] + Thread.current[:journaled_transactional_batching_enabled] = true + yield + ensure + Thread.current[:journaled_transactional_batching_enabled] = value_was + end def self.development_or_test? %w(development test).include?(Rails.env) diff --git a/lib/journaled/audit_log.rb b/lib/journaled/audit_log.rb index e59d77a..4912cdc 100644 --- a/lib/journaled/audit_log.rb +++ b/lib/journaled/audit_log.rb @@ -83,7 +83,7 @@ def enabled? def dup super.tap do |config| - config.ignored_columns = ignored_columns.dup + config.ignored_columns = ignored_columns.dup # rubocop:disable Rails/IgnoredColumnsAssignment config.enqueue_opts = enqueue_opts.dup end end diff --git a/lib/journaled/connection.rb b/lib/journaled/connection.rb index 5301a3b..8ea1eeb 100644 --- a/lib/journaled/connection.rb +++ b/lib/journaled/connection.rb @@ -2,11 +2,11 @@ module Journaled module Connection class << self def available? - Journaled.transactional_batching_enabled && transaction_open? + Journaled.transactional_batching_enabled? && transaction_open? end def stage!(event) - raise TransactionSafetyError, <<~MSG unless transaction_open? + raise TransactionSafetyError, <<~MSG unless available? Transaction not available! By default, journaled event batching requires an open database transaction. MSG diff --git a/lib/journaled/version.rb b/lib/journaled/version.rb index 7b5b5a8..a01b4db 100644 --- a/lib/journaled/version.rb +++ b/lib/journaled/version.rb @@ -1,3 +1,3 @@ module Journaled - VERSION = "5.2.0".freeze + VERSION = "5.3.0".freeze end diff --git a/spec/lib/journaled/connection_spec.rb b/spec/lib/journaled/connection_spec.rb index 6f70f5c..332cf63 100644 --- a/spec/lib/journaled/connection_spec.rb +++ b/spec/lib/journaled/connection_spec.rb @@ -27,7 +27,7 @@ end end - context 'when transactional batching is disabled' do + context 'when transactional batching is disabled globally' do around do |example| Journaled.transactional_batching_enabled = false example.run @@ -36,8 +36,23 @@ end it 'returns false, and raises an error when events are staged' do - expect(described_class.available?).to be false - expect { described_class.stage!(event) }.to raise_error(Journaled::TransactionSafetyError) + ActiveRecord::Base.transaction do + expect(described_class.available?).to be false + expect { described_class.stage!(event) }.to raise_error(Journaled::TransactionSafetyError) + end + end + + context 'but thread-local batching is enabled' do + around do |example| + Journaled.with_transactional_batching { example.run } + end + + it 'returns true, and allows for staging events' do + ActiveRecord::Base.transaction do + expect(described_class.available?).to be true + expect { described_class.stage!(event) }.not_to raise_error + end + end end end end diff --git a/spec/models/journaled/writer_spec.rb b/spec/models/journaled/writer_spec.rb index 75e25ff..b04d984 100644 --- a/spec/models/journaled/writer_spec.rb +++ b/spec/models/journaled/writer_spec.rb @@ -393,7 +393,7 @@ def fake_event(num) end end - context 'when transactional batching is disabled' do + context 'when transactional batching is disabled globally' do around do |example| Journaled.transactional_batching_enabled = false example.run @@ -415,6 +415,27 @@ def fake_event(num) .and journal_event_including(id: 'FAKE_UUID_1', event_type: 'fake_event_1') .and journal_event_including(id: 'FAKE_UUID_5', event_type: 'fake_event_5') end + + context 'but thread-local transactional batching is enabled' do + around do |example| + Journaled.with_transactional_batching { example.run } + end + + it 'batches multiple events and does not enqueue until the end of a transaction' do + expect { + ActiveRecord::Base.transaction do + expect { described_class.new(journaled_event: fake_event(1)).journal! } + .to not_change { enqueued_jobs.count }.from(0) + .and not_journal_event_including(id: 'FAKE_UUID_1', event_type: 'fake_event_1') + expect { described_class.new(journaled_event: fake_event(2)).journal! } + .to not_change { enqueued_jobs.count }.from(0) + .and not_journal_event_including(id: 'FAKE_UUID_2', event_type: 'fake_event_2') + end + }.to change { enqueued_jobs.count }.from(0).to(1) + .and journal_event_including(id: 'FAKE_UUID_1', event_type: 'fake_event_1') + .and journal_event_including(id: 'FAKE_UUID_2', event_type: 'fake_event_2') + end + end end context 'when an event is enqueued in its own before_commit callback' do