1
0
Fork 0
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:
David J. Hamilton 2014-09-15 14:19:14 -07:00 committed by Ryan Ringler
parent 23cfe86698
commit e396901fcd
6 changed files with 188 additions and 48 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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