lib/state_machine/transition.rb in state_machine-0.7.6 vs lib/state_machine/transition.rb in state_machine-0.8.0

- old
+ new

@@ -14,12 +14,12 @@ # Runs one or more transitions in parallel. All transitions will run # through the following steps: # 1. Before callbacks # 2. Persist state # 3. Invoke action - # 4. After callbacks if configured - # 5. Rollback if action is unsuccessful + # 4. After callbacks (if configured) + # 5. Rollback (if action is unsuccessful) # # Configuration options: # * <tt>:action</tt> - Whether to run the action configured for each transition # * <tt>:after</tt> - Whether to run after callbacks # @@ -44,11 +44,11 @@ success = if block_given? # Block was given: use the result for each transition result = yield transitions.each {|transition| results[transition.action] = result} - result + !!result elsif options[:action] == false # Skip the action true else # Run each transition's action (only once) @@ -62,12 +62,13 @@ # Action failed: rollback transitions.each {|transition| transition.rollback} raise end - # Always run after callbacks regardless of whether the actions failed - transitions.each {|transition| transition.after(results[transition.action])} unless options[:after] == false + # Run after callbacks even when the actions failed. The :after option + # is ignored if the transitions were unsuccessful. + transitions.each {|transition| transition.after(results[transition.action], success)} unless options[:after] == false && success # Rollback the transitions if the transaction was unsuccessful transitions.each {|transition| transition.rollback} unless success end @@ -122,11 +123,11 @@ # The result of invoking the action associated with the machine attr_reader :result # Creates a new, specific transition - def initialize(object, machine, event, from_name, to_name) #:nodoc: + def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc: @object = object @machine = machine @args = [] # Event information @@ -134,11 +135,11 @@ @event = event.name @qualified_event = event.qualified_name # From state information from_state = machine.states.fetch(from_name) - @from = machine.read(object, :state) + @from = read_state ? machine.read(object, :state) : from_state.value @from_name = from_state.name @qualified_from_name = from_state.qualified_name # To state information to_state = machine.states.fetch(to_name) @@ -216,10 +217,13 @@ # Runs the machine's +before+ callbacks for this transition. Only # callbacks that are configured to match the event, from state, and to # state will be invoked. # + # Once the callbacks are run, they cannot be run again until this transition + # is reset. + # # == Example # # class Vehicle # state_machine do # before_transition :on => :ignite, :do => lambda {|vehicle| ...} @@ -231,19 +235,24 @@ # transition.before def before result = false catch(:halt) do - callback(:before) + unless @before_run + callback(:before) + @before_run = true + end + result = true end result end # Transitions the current value of the state to that specified by the - # transition. + # transition. Once the state is persisted, it cannot be persisted again + # until this transition is reset. # # == Example # # class Vehicle # state_machine do @@ -257,20 +266,26 @@ # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling) # transition.persist # # vehicle.state # => 'idling' def persist - machine.write(object, :state, to) + unless @persisted + machine.write(object, :state, to) + @persisted = true + end end # Runs the machine's +after+ callbacks for this transition. Only # callbacks that are configured to match the event, from state, and to # state will be invoked. # - # The result is used to indicate whether the associated machine action + # The result can be used to indicate whether the associated machine action # was executed successfully. # + # Once the callbacks are run, they cannot be run again until this transition + # is reset. + # # == Halting # # If any callback throws a <tt>:halt</tt> exception, it will be caught # and the callback chain will be automatically stopped. However, this # exception will not bubble up to the caller since +after+ callbacks @@ -289,15 +304,18 @@ # end # # vehicle = Vehicle.new # transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling) # transition.after(true) - def after(result = nil) + def after(result = nil, success = true) @result = result catch(:halt) do - callback(:after) + unless @after_run + callback(:after, :success => success) + @after_run = true + end end true end @@ -324,13 +342,20 @@ # # # Roll back to the original state # transition.rollback # vehicle.state # => "parked" def rollback + reset machine.write(object, :state, from) end + # Resets any tracking of which callbacks have already been run and whether + # the state has already been persisted + def reset + @before_run = @persisted = @after_run = false + end + # Generates a nicely formatted description of this transitions's contents. # # For example, # # transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling) @@ -356,10 +381,12 @@ # only invoke callbacks that exactly match the event, from state, and # to state that describe this transition. # # Additional callback parameters can be specified. By default, this # transition is also passed into callbacks. - def callback(type) + def callback(type, context = {}) + context = self.context.merge(context) + machine.callbacks[type].each do |callback| callback.call(object, context, self) end end end