allow configuring behavior of nested transactions #107

This commit is contained in:
Thorsten Böttger 2014-01-22 21:28:40 +01:00
parent f6c903fae4
commit ca7306d0bb
5 changed files with 58 additions and 8 deletions

View File

@ -6,6 +6,7 @@
## 3.1.0 (not yet released) ## 3.1.0 (not yet released)
* allow configuring behavior of nested transactions (see [issue #107](https://github.com/aasm/aasm/issues/107))
* validating the current state (see [issue #95](https://github.com/aasm/aasm/issues/95), thanks to [@ivantsepp](https://github.com/ivantsepp)) * validating the current state (see [issue #95](https://github.com/aasm/aasm/issues/95), thanks to [@ivantsepp](https://github.com/ivantsepp))
## 3.0.26 ## 3.0.26

View File

@ -339,6 +339,28 @@ class Job < ActiveRecord::Base
end end
``` ```
If you want to encapsulate state changes within an own transaction, the behavior
of this nested transaction might be confusing. Take a look at
[ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
if you want to know more about this. Nevertheless, AASM by default requires a new transaction
`transaction(:requires_new => true)`. You can override this behavior by changing
the configuration
```ruby
class Job < ActiveRecord::Base
include AASM
aasm :requires_new_transaction => false do
...
end
...
end
```
which then leads to `transaction(:requires_new => false)`, the Rails default.
### Column name & migration ### Column name & migration
As a default AASM uses the column `aasm_state` to store the states. You can override As a default AASM uses the column `aasm_state` to store the states. You can override

View File

@ -23,6 +23,12 @@ module AASM
elsif @state_machine.config.skip_validation_on_save.nil? elsif @state_machine.config.skip_validation_on_save.nil?
@state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid @state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
end end
if options.key?(:requires_new_transaction)
@state_machine.config.requires_new_transaction = options[:requires_new_transaction]
elsif @state_machine.config.requires_new_transaction.nil?
@state_machine.config.requires_new_transaction = true # use requires_new for nested transactions
end
end end
def initial_state(new_initial_state=nil) def initial_state(new_initial_state=nil)

View File

@ -138,7 +138,7 @@ module AASM
end end
def aasm_fire_event(name, options, *args, &block) def aasm_fire_event(name, options, *args, &block)
success = self.class.transaction(:requires_new => true) do success = self.class.transaction(:requires_new => requires_new?) do
super super
end end
@ -150,6 +150,10 @@ module AASM
success success
end end
def requires_new?
AASM::StateMachine[self.class].config.requires_new_transaction
end
def aasm_validate_states def aasm_validate_states
unless AASM::StateMachine[self.class].config.skip_validation_on_save unless AASM::StateMachine[self.class].config.skip_validation_on_save
if aasm.current_state && !aasm.states.include?(aasm.current_state) if aasm.current_state && !aasm.states.include?(aasm.current_state)

View File

@ -173,6 +173,7 @@ describe 'transitions with persistence' do
expect(worker.reload.status).to eq('sleeping') expect(worker.reload.status).to eq('sleeping')
end end
context "nested transactions" do
it "should rollback all changes in nested transaction" do it "should rollback all changes in nested transaction" do
expect(transactor).to be_sleeping expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping') expect(worker.status).to eq('sleeping')
@ -185,6 +186,22 @@ describe 'transitions with persistence' do
expect(worker.reload.status).to eq('sleeping') expect(worker.reload.status).to eq('sleeping')
end end
it "should only rollback changes in the main transaction not the nested one" do
# change configuration to not require new transaction
AASM::StateMachine[Transactor].config.requires_new_transaction = false
expect(transactor).to be_sleeping
expect(worker.status).to eq('sleeping')
Worker.transaction do
expect { transactor.run! }.to raise_error(StandardError, 'failed on purpose')
end
expect(transactor).to be_running
expect(worker.reload.status).to eq('running')
end
end
describe "after_commit callback" do describe "after_commit callback" do
it "should fire :after_commit if transaction was successful" do it "should fire :after_commit if transaction was successful" do
validator = Validator.create(:name => 'name') validator = Validator.create(:name => 'name')