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