mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
Add :after_commit callbacks to events.
Event-specific :after_commit callbacks are called in the order that the events were called. They are called even when the record is manually persisted. For the sake of consistency, the same is now true of the state :after_commit callback. As in that case, this feature is specific to active record.
This commit is contained in:
parent
23cfe86698
commit
e396901fcd
6 changed files with 188 additions and 48 deletions
|
@ -115,11 +115,13 @@ module AASM
|
|||
|
||||
safely_define_method klass, "#{name}!", ->(*args, &block) do
|
||||
aasm(aasm_name).current_event = :"#{name}!"
|
||||
aasm(aasm_name).events_fired |= [[:"#{name}", *args]]
|
||||
aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
|
||||
end
|
||||
|
||||
safely_define_method klass, name, ->(*args, &block) do
|
||||
aasm(aasm_name).current_event = event
|
||||
aasm(aasm_name).events_fired |= [[:"#{name}", *args]]
|
||||
aasm_fire_event(aasm_name, event, {:persist => false}, *args, &block)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module AASM
|
||||
class InstanceBase
|
||||
|
||||
attr_accessor :from_state, :to_state, :current_event
|
||||
attr_accessor :from_state, :to_state, :current_event, :events_fired
|
||||
|
||||
def initialize(instance, name=:default) # instance of the class including AASM, name of the state machine
|
||||
@instance = instance
|
||||
@name = name
|
||||
@events_fired = []
|
||||
end
|
||||
|
||||
def current_state
|
||||
|
|
|
@ -33,6 +33,8 @@ module AASM
|
|||
|
||||
base.after_initialize :aasm_ensure_initial_state
|
||||
|
||||
base.after_commit :aasm_after_commit_hooks
|
||||
|
||||
# ensure state is in the list of states
|
||||
base.validate :aasm_validate_states
|
||||
end
|
||||
|
@ -189,8 +191,10 @@ module AASM
|
|||
end
|
||||
|
||||
if options[:persist] && success
|
||||
event.fire_callbacks(:after_commit, self, *args)
|
||||
event.fire_global_callbacks(:after_all_commits, self, *args)
|
||||
# Delegating to ActiveRecord
|
||||
#
|
||||
# event.fire_callbacks(:after_commit, self, *args)
|
||||
# event.fire_global_callbacks(:after_all_commits, self, *args)
|
||||
end
|
||||
ensure
|
||||
if options[:persist]
|
||||
|
@ -202,6 +206,28 @@ module AASM
|
|||
success
|
||||
end
|
||||
|
||||
def aasm_after_commit_hooks
|
||||
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
|
||||
unless aasm_invalid_state?(state_machine_name)
|
||||
new_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
|
||||
new_state.fire_callbacks(:after_commit, self)
|
||||
end
|
||||
|
||||
events_fired = aasm(state_machine_name).events_fired
|
||||
|
||||
until events_fired.empty?
|
||||
event_name, *args = events_fired.shift
|
||||
|
||||
self.class.aasm(state_machine_name).state_machine.events[event_name].fire_callbacks(:after_commit, self, *args)
|
||||
self.class.aasm(state_machine_name).state_machine.events[event_name].fire_global_callbacks(:after_all_commits, self, *args)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
AASM::StateMachineStore.fetch(self.class, true).machine_names.each do |state_machine_name|
|
||||
aasm(state_machine_name).events_fired.clear
|
||||
end
|
||||
end
|
||||
|
||||
def requires_new?(state_machine_name)
|
||||
AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.requires_new_transaction
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ class Validator < ActiveRecord::Base
|
|||
after_all_transactions :after_all_transactions
|
||||
|
||||
state :sleeping, :initial => true
|
||||
state :awake
|
||||
state :running
|
||||
state :failed, :after_enter => :fail
|
||||
|
||||
|
@ -31,10 +32,15 @@ class Validator < ActiveRecord::Base
|
|||
end
|
||||
|
||||
event :sleep do
|
||||
after_commit do |name|
|
||||
change_name_on_sleep name
|
||||
end
|
||||
transitions :to => :sleeping, :from => :running
|
||||
after_commit { append_name!(' slept') }
|
||||
|
||||
transitions :to => :sleeping, :from => [:running, :awake]
|
||||
end
|
||||
|
||||
event :wake do
|
||||
after_commit { |description| append_name!(" awoke#{description}") }
|
||||
|
||||
transitions :to => :awake, :from => [:sleeping]
|
||||
end
|
||||
|
||||
event :fail do
|
||||
|
@ -46,19 +52,19 @@ class Validator < ActiveRecord::Base
|
|||
@before_transaction_performed_on_fail = true
|
||||
end
|
||||
|
||||
transitions :to => :failed, :from => [:sleeping, :running]
|
||||
transitions :to => :failed, :from => [:sleeping, :running, :awake]
|
||||
end
|
||||
end
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
def change_name!
|
||||
self.name = "name changed"
|
||||
def append_name!(suffix)
|
||||
self.name += suffix
|
||||
save!
|
||||
end
|
||||
|
||||
def change_name_on_sleep name
|
||||
self.name = name
|
||||
def change_name!
|
||||
self.name = 'name changed'
|
||||
save!
|
||||
end
|
||||
|
||||
|
@ -76,36 +82,44 @@ class Validator < ActiveRecord::Base
|
|||
end
|
||||
|
||||
class MultipleValidator < ActiveRecord::Base
|
||||
|
||||
include AASM
|
||||
|
||||
aasm :left, :column => :status do
|
||||
state :sleeping, :initial => true
|
||||
state :awake
|
||||
state :running
|
||||
state :failed, :after_enter => :fail
|
||||
|
||||
event :run, :after_commit => :change_name! do
|
||||
transitions :to => :running, :from => :sleeping
|
||||
end
|
||||
|
||||
event :sleep do
|
||||
after_commit do |name|
|
||||
change_name_on_sleep name
|
||||
end
|
||||
transitions :to => :sleeping, :from => :running
|
||||
after_commit { append_name!(' slept') }
|
||||
|
||||
transitions :to => :sleeping, :from => [:running, :awake]
|
||||
end
|
||||
|
||||
event :wake do
|
||||
after_commit { |description| append_name!(" awoke#{description}") }
|
||||
|
||||
transitions :to => :awake, :from => [:sleeping]
|
||||
end
|
||||
|
||||
event :fail do
|
||||
transitions :to => :failed, :from => [:sleeping, :running]
|
||||
transitions :to => :failed, :from => [:sleeping, :running, :awake]
|
||||
end
|
||||
end
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
def change_name!
|
||||
self.name = "name changed"
|
||||
def append_name!(suffix)
|
||||
self.name += suffix
|
||||
save!
|
||||
end
|
||||
|
||||
def change_name_on_sleep name
|
||||
self.name = name
|
||||
def change_name!
|
||||
self.name = 'name changed'
|
||||
save!
|
||||
end
|
||||
|
||||
|
|
|
@ -268,16 +268,16 @@ describe "instance methods" do
|
|||
end
|
||||
|
||||
if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
|
||||
describe "direct state column access" do
|
||||
it "accepts false states" do
|
||||
f = MultipleFalseState.create!
|
||||
expect(f.aasm_state).to eql false
|
||||
expect {
|
||||
f.aasm(:left).events.map(&:name)
|
||||
}.to_not raise_error
|
||||
describe "direct state column access" do
|
||||
it "accepts false states" do
|
||||
f = MultipleFalseState.create!
|
||||
expect(f.aasm_state).to eql false
|
||||
expect {
|
||||
f.aasm(:left).events.map(&:name)
|
||||
}.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subclasses' do
|
||||
it "should have the same states as its parent class" do
|
||||
|
@ -467,7 +467,7 @@ describe 'transitions with persistence' do
|
|||
end
|
||||
|
||||
describe "after_commit callback" do
|
||||
it "should fire :after_commit if transaction was successful" do
|
||||
it "should fire state's :after_commit if transaction was successful" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
|
@ -475,25 +475,73 @@ describe 'transitions with persistence' do
|
|||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name changed")
|
||||
|
||||
validator.sleep!("sleeper")
|
||||
validator.sleep!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("sleeper")
|
||||
expect(validator.name).to eq("name changed slept")
|
||||
end
|
||||
|
||||
it "should not fire :after_commit if transaction failed" do
|
||||
it "should not fire state's :after_commit if transaction failed" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
|
||||
expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should not fire if not saving" do
|
||||
it "should fire state's :after_commit if manually persisted" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.run
|
||||
validator.save!
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name changed")
|
||||
end
|
||||
|
||||
it "should not immediately fire state's :after_commit if not saving" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.run
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should fire all events' :after_commit hooks in if saving" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake!(" feeling grumpy")
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name awoke feeling grumpy slept")
|
||||
end
|
||||
|
||||
it "should fire all events' :after_commit hooks in order if manually persisted" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake(" feeling grumpy")
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep
|
||||
validator.save!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name awoke feeling grumpy slept")
|
||||
end
|
||||
|
||||
it "should not immediately fire any events' :after_commit hooks if not saving" do
|
||||
validator = MultipleValidator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
end
|
||||
|
||||
context "when not persisting" do
|
||||
|
|
|
@ -268,16 +268,16 @@ describe "instance methods" do
|
|||
end
|
||||
|
||||
if ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 # won't work with Rails >= 4.2
|
||||
describe "direct state column access" do
|
||||
it "accepts false states" do
|
||||
f = FalseState.create!
|
||||
expect(f.aasm_state).to eql false
|
||||
expect {
|
||||
f.aasm.events.map(&:name)
|
||||
}.to_not raise_error
|
||||
describe "direct state column access" do
|
||||
it "accepts false states" do
|
||||
f = FalseState.create!
|
||||
expect(f.aasm_state).to eql false
|
||||
expect {
|
||||
f.aasm.events.map(&:name)
|
||||
}.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subclasses' do
|
||||
it "should have the same states as its parent class" do
|
||||
|
@ -501,7 +501,7 @@ describe 'transitions with persistence' do
|
|||
end
|
||||
|
||||
describe "after_commit callback" do
|
||||
it "should fire :after_commit if transaction was successful" do
|
||||
it "should fire state's :after_commit if transaction was successful" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
|
@ -509,24 +509,73 @@ describe 'transitions with persistence' do
|
|||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name changed")
|
||||
|
||||
validator.sleep!("sleeper")
|
||||
validator.sleep!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("sleeper")
|
||||
expect(validator.name).to eq("name changed slept")
|
||||
end
|
||||
|
||||
it "should not fire :after_commit if transaction failed" do
|
||||
it "should not fire state's :after_commit if transaction failed" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
|
||||
expect { validator.fail! }.to raise_error(StandardError, 'failed on purpose')
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should not fire if not saving" do
|
||||
it "should fire state's :after_commit if manually persisted" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.run
|
||||
validator.save!
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name changed")
|
||||
end
|
||||
|
||||
it "should not immediately fire state's :after_commit if not saving" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.run
|
||||
expect(validator).to be_running
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
|
||||
it "should fire all events' :after_commit hooks in if saving" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake!(" feeling grumpy")
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name awoke feeling grumpy slept")
|
||||
end
|
||||
|
||||
it "should fire all events' :after_commit hooks in order if manually persisted" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake(" feeling grumpy")
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep
|
||||
validator.save!
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name awoke feeling grumpy slept")
|
||||
end
|
||||
|
||||
it "should not immediately fire any events' :after_commit hooks if not saving" do
|
||||
validator = Validator.create(:name => 'name')
|
||||
expect(validator).to be_sleeping
|
||||
|
||||
validator.wake
|
||||
expect(validator).to be_awake
|
||||
|
||||
validator.sleep
|
||||
expect(validator).to be_sleeping
|
||||
expect(validator.name).to eq("name")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before and after transaction callbacks' do
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue