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:
commit
f34d152bc9
|
@ -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
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ begin
|
|||
new_state before_enter
|
||||
new_state enter
|
||||
...update state...
|
||||
transition success # if persist successful
|
||||
event success # if persist successful
|
||||
old_state after_exit
|
||||
new_state after_enter
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -118,7 +118,8 @@ describe 'callbacks for the new DSL' do
|
|||
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(: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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue