diff --git a/lib/aasm/aasm.rb b/lib/aasm/aasm.rb index 09385ca7..0ef49577 100644 --- a/lib/aasm/aasm.rb +++ b/lib/aasm/aasm.rb @@ -141,6 +141,20 @@ def aasm_human_state private + # Takes args and a from state and removes the first + # element from args if it is a valid to_state for + # the event given the from_state + def process_args(event, from_state, *args) + # If the first arg doesn't respond to to_sym then + # it isn't a symbol or string so it can't be a state + # name anyway + return args unless args.first.respond_to?(:to_sym) + if event.transitions_from_state(from_state).map(&:to).flatten.include?(args.first) + return args[1..-1] + end + return args + end + def aasm_fire_event(event_name, options, *args, &block) event = self.class.aasm_events[event_name] begin @@ -148,10 +162,14 @@ def aasm_fire_event(event_name, options, *args, &block) old_state.fire_callbacks(:exit, self) # new event before callback - event.fire_callbacks(:before, self) + event.fire_callbacks( + :before, + self, + *process_args(event, aasm.current_state, *args) + ) if new_state_name = event.fire(self, *args) - fired(event, old_state, new_state_name, options, &block) + fired(event, old_state, new_state_name, options, *args, &block) else failed(event_name, old_state) end @@ -160,7 +178,7 @@ def aasm_fire_event(event_name, options, *args, &block) end end - def fired(event, old_state, new_state_name, options) + def fired(event, old_state, new_state_name, options, *args) persist = options[:persist] new_state = aasm.state_object_for_name(new_state_name) @@ -186,7 +204,11 @@ def fired(event, old_state, new_state_name, options) if persist_successful old_state.fire_callbacks(:after_exit, self) new_state.fire_callbacks(:after_enter, self) - event.fire_callbacks(:after, self) + event.fire_callbacks( + :after, + self, + *process_args(event, old_state.name, *args) + ) self.aasm_event_fired(event.name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired) else diff --git a/lib/aasm/event.rb b/lib/aasm/event.rb index 7a3c8d5e..7d7ebf0a 100644 --- a/lib/aasm/event.rb +++ b/lib/aasm/event.rb @@ -43,6 +43,8 @@ def all_transitions end def fire_callbacks(callback_name, record, *args) + # strip out the first element in args if it's a valid to_state + # #given where we're coming from, this condition implies args not empty invoke_callbacks(@options[callback_name], record, args) end @@ -68,12 +70,22 @@ def update(options = {}, &block) def _fire(obj, test, to_state=nil, *args) result = test ? false : nil if @transitions.map(&:from).any? - transitions = @transitions.select { |t| t.from == obj.aasm_current_state } + transitions = transitions_from_state(obj.aasm_current_state) return result if transitions.size == 0 else transitions = @transitions end + # If to_state is not nil it either contains a potential + # to_state or an arg + unless to_state == nil + if to_state.respond_to?(:to_sym) && + !transitions.map(&:to).flatten.include?(to_state.to_sym) + args.unshift(to_state) + to_state = nil + end + end + transitions.each do |transition| next if to_state and !Array(transition.to).include?(to_state) if transition.perform(obj, *args) diff --git a/spec/models/callback_new_dsl.rb b/spec/models/callback_new_dsl.rb index 7fa517a6..d0e89af2 100644 --- a/spec/models/callback_new_dsl.rb +++ b/spec/models/callback_new_dsl.rb @@ -41,3 +41,74 @@ def after; end def enter_closed; end def exit_open; end end + +class CallbackNewDslArgs + include AASM + + aasm do + state :open, :initial => true, + :before_enter => :before_enter_open, + :after_enter => :after_enter_open, + :before_exit => :before_exit_open, + :exit => :exit_open, + :after_exit => :after_exit_open + + state :closed, + :before_enter => :before_enter_closed, + :enter => :enter_closed, + :after_enter => :after_enter_closed, + :before_exit => :before_exit_closed, + :after_exit => :after_exit_closed + + event :close, :before => :before, :after => :after do + transitions :to => :closed, :from => [:open], :on_transition => :transition_proc + end + + event :open, :before => :before, :after => :after do + transitions :to => :open, :from => :closed + end + end + + def before_enter_open; end + def before_exit_open; end + def after_enter_open; end + def after_exit_open; end + + def before_enter_closed; end + def before_exit_closed; end + def after_enter_closed; end + def after_exit_closed; end + + def before(*args); end + def transition_proc(arg1, arg2); end + def after(*args); end + + def enter_closed; end + def exit_open; end + +end + +class CallbackWithStateArg + + include AASM + + aasm do + state :open, :inital => true + state :closed + state :out_to_lunch + + event :close, :before => :before_method, :after => :after_method do + transitions :to => :closed, :from => [:open], :on_transition => :transition_method + transitions :to => :out_to_lunch, :from => [:open], :on_transition => :transition_method2 + end + end + + def before_method(arg); end + + def after_method(arg); end + + def transition_method(arg); end + + def transition_method2(arg); end + +end diff --git a/spec/unit/callbacks_spec.rb b/spec/unit/callbacks_spec.rb index b1faad63..89b7a0fb 100644 --- a/spec/unit/callbacks_spec.rb +++ b/spec/unit/callbacks_spec.rb @@ -34,6 +34,38 @@ callback.close! end + + it "should properly pass arguments" do + cb = CallbackNewDslArgs.new + cb.should_receive(:exit_open).once.ordered + cb.should_receive(:before).with(:arg1, :arg2).once.ordered + cb.should_receive(:transition_proc).with(:arg1, :arg2).once.ordered + cb.should_receive(:before_exit_open).once.ordered # these should be before the state changes + cb.should_receive(:before_enter_closed).once.ordered + cb.should_receive(:enter_closed).once.ordered + cb.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes + cb.should_receive(:after_exit_open).once.ordered # these should be after the state changes + cb.should_receive(:after_enter_closed).once.ordered + cb.should_receive(:after).with(:arg1, :arg2).once.ordered + + cb.close!(:arg1, :arg2) + end + + it "should call the proper methods given a to state as the first arg" do + cb = CallbackWithStateArg.new + cb.should_receive(:before_method).with(:arg1).once.ordered + cb.should_receive(:transition_method).with(:arg1).once.ordered + cb.should_receive(:transition_method).never + cb.should_receive(:after_method).with(:arg1).once.ordered + cb.close!(:arg1) + + cb = CallbackWithStateArg.new + cb.should_receive(:before_method).with(:arg1).once.ordered + cb.should_receive(:transition_method).never + cb.should_receive(:transition_method2).with(:arg1).once.ordered + cb.should_receive(:after_method).with(:arg1).once.ordered + cb.close!(:out_to_lunch, :arg1) + end end describe 'event callbacks' do