From c5b7efaab2389eb89c93e8abf8f31e03f4b72f72 Mon Sep 17 00:00:00 2001 From: Peter Lampesberger Date: Tue, 30 May 2017 23:22:38 +0200 Subject: [PATCH] Add use transaction flag --- README.md | 18 +++++++++++++ lib/aasm/base.rb | 3 +++ lib/aasm/configuration.rb | 3 +++ lib/aasm/persistence/orm.rb | 10 +++++++- spec/database.rb | 2 +- spec/models/active_record/transactor.rb | 25 +++++++++++++++++++ .../active_record_persistence_spec.rb | 14 +++++++++++ 7 files changed, 73 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 839f678..ebbb896 100644 --- a/README.md +++ b/README.md @@ -957,6 +957,24 @@ end which then leads to `transaction(:requires_new => false)`, the Rails default. +Additionally, if you do not want any of your active record actions to be +wrapped in a transaction, you can specify the `use_transactions` flag. This can +be useful if you want want to persist things to the database that happen as a +result of a transaction or callback, even when some error occurs. The +`use_transactions` flag is true by default. + +```ruby +class Job < ActiveRecord::Base + include AASM + + aasm :use_transactions => false do + ... + end + + ... +end +``` + ### Pessimistic Locking AASM supports [Active Record pessimistic locking via `with_lock`](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#method-i-with_lock) for database persistence layers. diff --git a/lib/aasm/base.rb b/lib/aasm/base.rb index 38786df..08e8407 100644 --- a/lib/aasm/base.rb +++ b/lib/aasm/base.rb @@ -26,6 +26,9 @@ module AASM # raise if the model is invalid (in ActiveRecord) configure :whiny_persistence, false + # Use transactions (in ActiveRecord) + configure :use_transactions, true + # use requires_new for nested transactions (in ActiveRecord) configure :requires_new_transaction, true diff --git a/lib/aasm/configuration.rb b/lib/aasm/configuration.rb index ab10f2d..e32eb39 100644 --- a/lib/aasm/configuration.rb +++ b/lib/aasm/configuration.rb @@ -15,6 +15,9 @@ module AASM # for ActiveRecord: store the new state even if the model is invalid and return true attr_accessor :skip_validation_on_save + # for ActiveRecord: use transactions + attr_accessor :use_transactions + # for ActiveRecord: use requires_new for nested transactions? attr_accessor :requires_new_transaction diff --git a/lib/aasm/persistence/orm.rb b/lib/aasm/persistence/orm.rb index 45b0386..9b4f949 100644 --- a/lib/aasm/persistence/orm.rb +++ b/lib/aasm/persistence/orm.rb @@ -102,6 +102,10 @@ module AASM AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.skip_validation_on_save end + def use_transactions?(state_machine_name) + AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.use_transactions + end + def requires_new?(state_machine_name) AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction end @@ -118,7 +122,11 @@ module AASM event.fire_global_callbacks(:before_all_transactions, self, *args) begin - success = aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do + success = if options[:persist] && use_transactions?(state_machine_name) + aasm_transaction(requires_new?(state_machine_name), requires_lock?(state_machine_name)) do + super + end + else super end diff --git a/spec/database.rb b/spec/database.rb index b004487..620cb77 100644 --- a/spec/database.rb +++ b/spec/database.rb @@ -24,7 +24,7 @@ ActiveRecord::Migration.suppress_messages do end end - %w(transactors no_lock_transactors lock_transactors lock_no_wait_transactors multiple_transactors).each do |table_name| + %w(transactors no_lock_transactors lock_transactors lock_no_wait_transactors no_transactors multiple_transactors).each do |table_name| ActiveRecord::Migration.create_table table_name, :force => true do |t| t.string "name" t.string "status" diff --git a/spec/models/active_record/transactor.rb b/spec/models/active_record/transactor.rb index 12af98e..d3d1772 100644 --- a/spec/models/active_record/transactor.rb +++ b/spec/models/active_record/transactor.rb @@ -72,6 +72,31 @@ class LockNoWaitTransactor < ActiveRecord::Base end end +class NoTransactor < ActiveRecord::Base + + belongs_to :worker + + include AASM + aasm :column => :status, use_transactions: false do + state :sleeping, :initial => true + state :running, :before_enter => :start_worker, :after_enter => :fail + + event :run do + transitions :to => :running, :from => :sleeping + end + end + + private + + def start_worker + worker.update_attribute(:status, 'running') + end + + def fail + raise StandardError.new('failed on purpose') + end +end + class MultipleTransactor < ActiveRecord::Base belongs_to :worker diff --git a/spec/unit/persistence/active_record_persistence_spec.rb b/spec/unit/persistence/active_record_persistence_spec.rb index 5983ae1..1a507c9 100644 --- a/spec/unit/persistence/active_record_persistence_spec.rb +++ b/spec/unit/persistence/active_record_persistence_spec.rb @@ -506,6 +506,20 @@ if defined?(ActiveRecord) end end + describe 'without transactions' do + let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') } + let(:no_transactor) { NoTransactor.create!(:name => 'transactor', :worker => worker) } + + it 'should not rollback all changes' do + expect(no_transactor).to be_sleeping + expect(worker.status).to eq('sleeping') + + expect {no_transactor.run!}.to raise_error(StandardError, 'failed on purpose') + expect(no_transactor).to be_running + expect(worker.reload.status).to eq('running') + end + end + describe 'transactions' do let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') } let(:transactor) { Transactor.create!(:name => 'transactor', :worker => worker) }