Merge branch 'transition_success_callbacks' of https://github.com/brega/aasm into brega-transition_success_callbacks

Conflicts:
	lib/aasm/core/transition.rb
	spec/models/callbacks/basic.rb
	spec/unit/transition_spec.rb
This commit is contained in:
Thorsten Böttger 2016-03-19 17:42:35 +13:00
commit f34d152bc9
14 changed files with 164 additions and 21 deletions

View File

@ -10,6 +10,7 @@
* improve: warn when overriding an existing method (see [issue #340](https://github.com/aasm/aasm/pull/340) which fixes [issue #335](https://github.com/aasm/aasm/pull/335) for details, thanks to [@pirj](https://github.com/pirj))
* fix: correct error message (by not evaluating the current state lazily) (see [issue #341](https://github.com/aasm/aasm/pull/341) which fixes [issue #312](https://github.com/aasm/aasm/pull/312) for details, thanks to [@pirj](https://github.com/pirj))
* addition: support for Redis as persistence layer (see [issue #190](https://github.com/aasm/aasm/pull/190) for details, thanks to [@javajax](https://github.com/javajax))
* addition: support transition `:success` callbacks (see [issue #239](https://github.com/aasm/aasm/pull/239) which fixes [issue #236](https://github.com/aasm/aasm/pull/236) for details, thanks to [@brega](https://github.com/brega))
## 4.9.0

View File

@ -159,7 +159,7 @@ class LogRunTime
def initialize(job, args = {})
@job = job
end
def call
log "Job was running for #{@job.run_time} seconds"
end
@ -181,7 +181,8 @@ begin
new_state before_enter
new_state enter
...update state...
event success # if persist successful
transition success # if persist successful
event success # if persist successful
old_state after_exit
new_state after_enter
event after

View File

@ -149,6 +149,7 @@ private
persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
if persist_successful
yield if block_given?
event.fire_transition_callbacks(self, *process_args(event, old_state.name, *args))
event.fire_callbacks(:success, self)
end
else

View File

@ -8,6 +8,7 @@ module AASM::Core
@name = name
@state_machine = state_machine
@transitions = []
@valid_transitions = {}
@guards = Array(options[:guard] || options[:guards] || options[:if])
@unless = Array(options[:unless]) #TODO: This could use a better name
@ -62,6 +63,12 @@ module AASM::Core
invoke_callbacks(@options[callback_name], record, args)
end
def fire_transition_callbacks(obj, *args)
from_state = obj.aasm(state_machine.name).current_state
transition = @valid_transitions[from_state]
@valid_transitions[from_state].invoke_success_callbacks(obj, *args) if transition
end
def ==(event)
if event.is_a? Symbol
name == event
@ -103,7 +110,6 @@ module AASM::Core
definitions
end
# Execute if test == false, otherwise return true/false depending on whether it would fire
def _fire(obj, options={}, to_state=nil, *args)
result = options[:test_only] ? false : nil
if @transitions.map(&:from).any?
@ -130,6 +136,7 @@ module AASM::Core
if options[:test_only]
# result = true
else
Array(transition.to).each {|to| @valid_transitions[to] = transition }
transition.execute(obj, *args)
end

View File

@ -6,7 +6,7 @@ module AASM::Core
alias_method :options, :opts
def initialize(event, opts, &block)
add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
add_options_from_dsl(opts, [:on_transition, :guard, :after, :success], &block) if block
@event = event
@from = opts[:from]
@ -22,6 +22,9 @@ module AASM::Core
@after = Array(opts[:after])
@after = @after[0] if @after.size == 1
@success = Array(opts[:success])
@success = @success[0] if @success.size == 1
@opts = opts
end
@ -43,6 +46,10 @@ module AASM::Core
@from == value
end
def invoke_success_callbacks(obj, *args)
_fire_callbacks(@success, obj, args)
end
private
def invoke_callbacks_compatible_with_guard(code, record, args, options={})
@ -110,5 +117,19 @@ module AASM::Core
end
end
def _fire_callbacks(code, record, args)
case code
when Symbol, String
arity = record.send(:method, code.to_sym).arity
record.send(code, *(arity < 0 ? args : args[0...arity]))
when Proc
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
when Array
@success.map {|a| _fire_callbacks(a, obj, args)}
else
true
end
end
end
end # AASM

View File

@ -39,8 +39,15 @@ module Callbacks
:exit => :exit_closed,
:after_exit => :after_exit_closed
event :close, :before => :before_event, :after => :after_event, :guard => :event_guard, :ensure => :ensure_event do
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :after_transition
event :close,
:before => :before_event,
:after => :after_event,
:guard => :event_guard,
:ensure => :ensure_event do
transitions :to => :closed, :from => [:open],
:guard => :transition_guard,
:after => :after_transition,
:success => :success_transition
end
event :open, :before => :before_event, :after => :after_event do
@ -79,6 +86,9 @@ module Callbacks
def after_event; log('after_event'); end
def after_all_events; log('after_all_events'); end
def after_transition; log('after_transition'); end
def success_transition; log('transition_success'); end
def ensure_event; log('ensure'); end
def ensure_on_all_events; log('ensure'); end
end

View File

@ -26,7 +26,7 @@ module Callbacks
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :after => :transitioning do
transitions :to => :closed, :from => [:open], :after => :transitioning, :success => :success_transition do
guard do
transition_guard
end
@ -59,6 +59,7 @@ module Callbacks
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def success_transition; log('success transition'); end
def before; log('before'); end
def after; log('after'); end

View File

@ -28,7 +28,7 @@ module Callbacks
state :failed
event :close, :before => :before, :after => :after, :guard => :event_guard do
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning, :success => :success_transition
transitions :to => :failed, :from => [:open]
end
@ -58,6 +58,7 @@ module Callbacks
def event_guard; log('event_guard'); !@fail_event_guard; end
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
def transitioning; log('transitioning'); end
def success_transition; log('transition success'); end
def before; log('before'); end
def after; log('after'); end

View File

@ -29,7 +29,7 @@ module Callbacks
:after_exit => :after_exit_closed
event :close, :before => :before, :after => :after do
transitions :to => :closed, :from => [:open], :after => :transition_proc
transitions :to => :closed, :from => [:open], :after => :transition_proc, :success => :transition_success_proc
end
event :open, :before => :before, :after => :after do
@ -57,5 +57,6 @@ module Callbacks
def before(arg1, *args); log("before(#{arg1.inspect},#{args.map(&:inspect).join(',')})"); end
def transition_proc(arg1, arg2); log("transition_proc(#{arg1.inspect},#{arg2.inspect})"); end
def after(*args); log("after(#{args.map(&:inspect).join(',')})"); end
def transition_success_proc(*args); log("transition_success(#{args.map(&:inspect).join(',')})"); end
end
end

View File

@ -9,8 +9,8 @@ module Callbacks
state :out_to_lunch
event :close, :before => :before_method, :after => :after_method do
transitions :to => :closed, :from => [:open], :after => :transition_method
transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2
transitions :to => :closed, :from => [:open], :after => :transition_method, :success => :success_method
transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2, :success => :success_method2
end
end
@ -22,5 +22,9 @@ module Callbacks
def transition_method2(arg); end
def success_method(arg); end
def success_method2(arg); end
end
end

View File

@ -12,18 +12,24 @@ class ParametrisedEvent
end
event :dress do
transitions :from => :sleeping, :to => :working, :after => :wear_clothes
transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }
transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair]
transitions :from => :sleeping, :to => :working, :after => :wear_clothes, :success => :wear_makeup
transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }, :success => proc { |*args| wear_makeup(*args) }
transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair], :success => [:touch_up_hair]
end
end
def wear_clothes(shirt_color, trouser_type=nil)
end
def wear_makeup(makeup, moisturizer)
end
def condition_hair
end
def fix_hair
end
def touch_up_hair
end
end

View File

@ -109,7 +109,7 @@ describe 'callbacks for the new DSL' do
expect(callback).to receive(:before_event).once.ordered
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
expect(callback).to receive(:before_exit_open).once.ordered # these should be before the state changes
expect(callback).to receive(:before_exit_open).once.ordered # these should be before the state changes
expect(callback).to receive(:exit_open).once.ordered
# expect(callback).to receive(:event_guard).once.ordered.and_return(true)
# expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
@ -117,8 +117,9 @@ describe 'callbacks for the new DSL' do
expect(callback).to receive(:after_transition).once.ordered
expect(callback).to receive(:before_enter_closed).once.ordered
expect(callback).to receive(:enter_closed).once.ordered
expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
expect(callback).to receive(:after_exit_open).once.ordered # these should be after the state changes
expect(callback).to receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
expect(callback).to receive(:success_transition).once.ordered.and_return(true) # these should be after the state changes
expect(callback).to receive(:after_exit_open).once.ordered
expect(callback).to receive(:after_enter_closed).once.ordered
expect(callback).to receive(:after_event).once.ordered
end
@ -142,6 +143,7 @@ describe 'callbacks for the new DSL' do
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:success_transition)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after_event)
@ -184,6 +186,7 @@ describe 'callbacks for the new DSL' do
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:success_transition)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after_event)
@ -214,6 +217,7 @@ describe 'callbacks for the new DSL' do
expect(callback).to receive(:after).once.ordered
expect(callback).to_not receive(:transitioning)
expect(callback).to_not receive(:success_transition)
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:after_enter_closed)
@ -236,6 +240,7 @@ describe 'callbacks for the new DSL' do
expect(callback).to_not receive(:before_enter_closed)
expect(callback).to_not receive(:enter_closed)
expect(callback).to_not receive(:aasm_write_state)
expect(callback).to_not receive(:success_transition)
expect(callback).to_not receive(:after_exit_open)
expect(callback).to_not receive(:after_enter_closed)
expect(callback).to_not receive(:after)
@ -252,14 +257,16 @@ describe 'callbacks for the new DSL' do
cb.reset_data
cb.close!(:arg1, :arg2)
expect(cb.data).to eql 'before(:arg1,:arg2) before_exit_open(:arg1,:arg2) transition_proc(:arg1,:arg2) before_enter_closed(:arg1,:arg2) aasm_write_state after_exit_open(:arg1,:arg2) after_enter_closed(:arg1,:arg2) after(:arg1,:arg2)'
expect(cb.data).to eql 'before(:arg1,:arg2) before_exit_open(:arg1,:arg2) transition_proc(:arg1,:arg2) before_enter_closed(:arg1,:arg2) aasm_write_state transition_success(:arg1,:arg2) after_exit_open(:arg1,:arg2) after_enter_closed(:arg1,:arg2) after(:arg1,:arg2)'
end
it "should call the callbacks given the to-state as argument" do
cb = Callbacks::WithStateArg.new
expect(cb).to receive(:before_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:success_method).never
expect(cb).to receive(:transition_method2).with(:arg1).once.ordered
expect(cb).to receive(:success_method2).with(:arg1).once.ordered
expect(cb).to receive(:after_method).with(:arg1).once.ordered
cb.close!(:out_to_lunch, :arg1)
@ -267,6 +274,7 @@ describe 'callbacks for the new DSL' do
some_object = double('some object')
expect(cb).to receive(:before_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method2).with(some_object).once.ordered
expect(cb).to receive(:success_method2).with(some_object).once.ordered
expect(cb).to receive(:after_method).with(some_object).once.ordered
cb.close!(:out_to_lunch, some_object)
end
@ -276,6 +284,8 @@ describe 'callbacks for the new DSL' do
expect(cb).to receive(:before_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).with(:arg1).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:success_method).with(:arg1).once.ordered
expect(cb).to receive(:success_method).never
expect(cb).to receive(:after_method).with(:arg1).once.ordered
cb.close!(:arg1)
@ -284,6 +294,8 @@ describe 'callbacks for the new DSL' do
expect(cb).to receive(:before_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method).with(some_object).once.ordered
expect(cb).to receive(:transition_method).never
expect(cb).to receive(:success_method).with(some_object).once.ordered
expect(cb).to receive(:success_method).never
expect(cb).to receive(:after_method).with(some_object).once.ordered
cb.close!(some_object)
end
@ -299,7 +311,7 @@ describe 'event callbacks' do
aasm do
event :safe_close, :success => :success_callback, :error => :error_callback do
transitions :to => :closed, :from => [:open]
transitions :to => :closed, :from => [:open], :success => :transition_success_callback
end
end
end

View File

@ -6,7 +6,7 @@ describe 'adding an event' do
AASM::Core::Event.new(:close_order, state_machine, {:success => :success_callback}) do
before :before_callback
after :after_callback
transitions :to => :closed, :from => [:open, :received]
transitions :to => :closed, :from => [:open, :received], success: [:transition_success_callback]
end
end
@ -337,6 +337,24 @@ describe 'parametrised events' do
expect(pe).to receive(:fix_hair)
pe.dress!(:prettying_up)
end
it 'should call :success transition method with args' do
pe.wakeup!(:showering)
expect(pe).to receive(:wear_makeup).with('foundation', 'SPF')
pe.dress!(:working, 'foundation', 'SPF')
end
it 'should call :success transition proc' do
pe.wakeup!(:showering)
expect(pe).to receive(:wear_makeup).with('purple', 'slacks')
pe.dress!(:dating, 'purple', 'slacks')
end
it 'should call :success transition with an array of methods' do
pe.wakeup!(:showering)
expect(pe).to receive(:touch_up_hair)
pe.dress!(:prettying_up)
end
end
describe 'event firing without persistence' do

View File

@ -79,15 +79,17 @@ describe AASM::Core::Transition do
expect(st.opts[:after]).to eql [:after_callback]
end
it 'should set after and guard from dsl' do
it 'should set after, guard and success from dsl' do
opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
st = AASM::Core::Transition.new(event, opts) do
guard :gg
after :after_callback
success :after_persist
end
expect(st.opts[:guard]).to eql ['g', :gg]
expect(st.opts[:after]).to eql [:after_callback] # TODO fix this bad code coupling
expect(st.opts[:success]).to eql [:after_persist] # TODO fix this bad code coupling
end
it 'should pass equality check if from and to are the same' do
@ -363,3 +365,60 @@ describe AASM::Core::Transition, '- when executing the transition with a Class'
expect(return_value).to eq('success')
end
end
describe AASM::Core::Transition, '- when invoking the transition :success method call' do
let(:state_machine) { AASM::StateMachine.new(:name) }
let(:event) { AASM::Core::Event.new(:event, state_machine) }
it 'should accept a String for the method name' do
opts = {:from => 'foo', :to => 'bar', :success => 'test'}
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
expect(obj).to receive(:test)
st.invoke_success_callbacks(obj, args)
end
it 'should accept a Symbol for the method name' do
opts = {:from => 'foo', :to => 'bar', :success => :test}
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
expect(obj).to receive(:test)
st.invoke_success_callbacks(obj, args)
end
it 'should pass args if the target method accepts them' do
opts = {:from => 'foo', :to => 'bar', :success => :test}
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
def obj.test(args)
"arg1: #{args[:arg1]} arg2: #{args[:arg2]}"
end
return_value = st.invoke_success_callbacks(obj, args)
expect(return_value).to eq('arg1: 1 arg2: 2')
end
it 'should NOT pass args if the target method does NOT accept them' do
opts = {:from => 'foo', :to => 'bar', :success => :test}
st = AASM::Core::Transition.new(event, opts)
args = {:arg1 => '1', :arg2 => '2'}
obj = double('object', :aasm => 'aasm')
def obj.test
'success'
end
return_value = st.invoke_success_callbacks(obj, args)
expect(return_value).to eq('success')
end
end