lib/state_machine/event.rb in state_machine-0.4.3 vs lib/state_machine/event.rb in state_machine-0.5.0

- old
+ new

@@ -32,57 +32,53 @@ add_actions end # Creates a copy of this event in addition to the list of associated - # guards to prevent conflicts across different events. + # guards to prevent conflicts across events within a class hierarchy. def initialize_copy(orig) #:nodoc: super @guards = @guards.dup @known_states = @known_states.dup end # Creates a new transition that will be evaluated when the event is fired. # # Configuration options: - # * +to+ - The state that's being transitioned to. If not specified, then the transition will not change the state. - # * +from+ - A state or array of states that can be transitioned from. If not specified, then the transition can occur for *any* from state. - # * +except_from+ - A state or array of states that *cannot* be transitioned from. - # * +if+ - Specifies a method, proc or string to call to determine if the transition should occur (e.g. :if => :moving?, or :if => Proc.new {|car| car.speed > 60}). The method, proc or string should return or evaluate to a true or false value. - # * +unless+ - Specifies a method, proc or string to call to determine if the transition should not occur (e.g. :unless => :stopped?, or :unless => Proc.new {|car| car.speed <= 60}). The method, proc or string should return or evaluate to a true or false value. + # * <tt>:from</tt> - A state or array of states that can be transitioned from. + # If not specified, then the transition can occur for *any* state. + # * <tt>:to</tt> - The state that's being transitioned to. If not specified, + # then the transition will simply loop back (i.e. the state will not change). + # * <tt>:except_from</tt> - A state or array of states that *cannot* be + # transitioned from. + # * <tt>:if</tt> - A method, proc or string to call to determine if the + # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}). + # The condition should return or evaluate to true or false. + # * <tt>:unless</tt> - A method, proc or string to call to determine if the + # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}). + # The condition should return or evaluate to true or false. # # == Order of operations # # Transitions are evaluated in the order in which they're defined. As a # result, if more than one transition applies to a given object, then the # first transition that matches will be performed. # - # == Dynamic states - # - # There is limited support for using dynamically generated values for the - # +to+ state in transitions. This is especially useful for times where - # the machine attribute represents a Time object. In order to have a - # a transition be made to the current time, a lambda block can be passed - # in representing the state, such as: - # - # transition :to => lambda {Time.now} - # # == Examples # - # transition :from => nil, :to => 'parked' - # transition :from => %w(first_gear reverse) - # transition :except_from => 'parked' + # transition :from => nil, :to => :parked + # transition :from => [:first_gear, :reverse] + # transition :except_from => :parked # transition :to => nil - # transition :to => 'parked' - # transition :to => lambda {Time.now} - # transition :to => 'parked', :from => 'first_gear' - # transition :to => 'parked', :from => %w(first_gear reverse) - # transition :to => 'parked', :from => 'first_gear', :if => :moving? - # transition :to => 'parked', :from => 'first_gear', :unless => :stopped? - # transition :to => 'parked', :except_from => 'parked' + # transition :to => :parked + # transition :to => :parked, :from => :first_gear + # transition :to => :parked, :from => [:first_gear, :reverse] + # transition :to => :parked, :from => :first_gear, :if => :moving? + # transition :to => :parked, :from => :first_gear, :unless => :stopped? + # transition :to => :parked, :except_from => :parked def transition(options) - assert_valid_keys(options, :to, :from, :except_from, :if, :unless) + assert_valid_keys(options, :from, :to, :except_from, :if, :unless) guards << guard = Guard.new(options) @known_states |= guard.known_states guard end @@ -96,41 +92,62 @@ end # Finds and builds the next transition that can be performed on the given # object. If no transitions can be made, then this will return nil. def next_transition(object) - from = object.send(machine.attribute) + from = machine.state_for(object).name if guard = guards.find {|guard| guard.matches?(object, :from => from)} # Guard allows for the transition to occur to = guard.requirements[:to] ? guard.requirements[:to].first : from - to = to.call if to.is_a?(Proc) Transition.new(object, machine, name, from, to) end end # Attempts to perform the next available transition on the given object. # If no transitions can be made, then this will return false, otherwise # true. + # + # Any additional arguments are passed to the StateMachine::Transition#perform + # instance method. def fire(object, *args) if transition = next_transition(object) transition.perform(*args) else false end end + # Attempts to perform the next available transition on the given object. + # If no transitions can be made, then a StateMachine::InvalidTransition + # exception will be raised, otherwise true will be returned. + def fire!(object, *args) + fire(object, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{machine.attribute} via :#{name} from #{machine.state_for(object).name.inspect}") + end + # Draws a representation of this event on the given graph. This will # create 1 or more edges on the graph for each guard (i.e. transition) # configured. # # A collection of the generated edges will be returned. def draw(graph) - valid_states = machine.states_order + valid_states = machine.states.by_priority.map {|state| state.name} guards.collect {|guard| guard.draw(graph, name, valid_states)}.flatten end + # Generates a nicely formatted description of this events's contents. + # + # For example, + # + # event = StateMachine::Event.new(machine, :park) + # event.transition :to => :parked, :from => :idling + # event # => #<StateMachine::Event name=:park transitions=[{:to => [:parked], :from => [:idling]}]> + def inspect + attributes = [[:name, name], [:transitions, guards.map {|guard| guard.requirements}]] + "#<#{self.class} #{attributes.map {|name, value| "#{name}=#{value.inspect}"} * ' '}>" + end + protected # Add the various instance methods that can transition the object using # the current event def add_actions attribute = machine.attribute @@ -141,22 +158,23 @@ # Checks whether the event can be fired on the current object define_method("can_#{qualified_name}?") do self.class.state_machines[attribute].event(name).can_fire?(self) end - # Gets the next transition that would be performed if the event were to be fired now + # Gets the next transition that would be performed if the event were + # fired now define_method("next_#{qualified_name}_transition") do self.class.state_machines[attribute].event(name).next_transition(self) end # Fires the event define_method(qualified_name) do |*args| self.class.state_machines[attribute].event(name).fire(self, *args) end - # Fires the event, raising an exception if it fails to transition + # Fires the event, raising an exception if it fails define_method("#{qualified_name}!") do |*args| - send(qualified_name, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{attribute} via :#{name} from #{send(attribute).inspect}") + self.class.state_machines[attribute].event(name).fire!(self, *args) end end end end end