Class: StateMachine::Event
- Inherits:
-
Object
- Object
- StateMachine::Event
- Includes:
- Assertions, MatcherHelpers
- Defined in:
- lib/state_machine/event.rb
Overview
An event defines an action that transitions an attribute from one state to another. The state that an attribute is transitioned to depends on the guards configured for the event.
Instance Attribute Summary collapse
-
#guards ⇒ Object
readonly
The list of guards that determine what state this event transitions objects to when fired.
-
#human_name(klass = @machine.owner_class) ⇒ Object
Transforms the event name into a more human-readable format, such as “turn on” instead of “turn_on”.
-
#known_states ⇒ Object
readonly
A list of all of the states known to this event using the configured guards/transitions as the source.
-
#machine ⇒ Object
The state machine for which this event is defined.
-
#name ⇒ Object
readonly
The name of the event.
-
#qualified_name ⇒ Object
readonly
The fully-qualified name of the event, scoped by the machine’s namespace.
Instance Method Summary collapse
-
#can_fire?(object) ⇒ Boolean
Determines whether any transitions can be performed for this event based on the current state of the given object.
-
#draw(graph) ⇒ Object
Draws a representation of this event on the given graph.
-
#fire(object, *args) ⇒ Object
Attempts to perform the next available transition on the given object.
-
#initialize(machine, name, options = {}) ⇒ Event
constructor
Creates a new event within the context of the given machine.
-
#initialize_copy(orig) ⇒ Object
Creates a copy of this event in addition to the list of associated guards to prevent conflicts across events within a class hierarchy.
-
#inspect ⇒ Object
Generates a nicely formatted description of this event’s contents.
-
#transition(options) ⇒ Object
Creates a new transition that determines what to change the current state to when this event fires.
-
#transition_for(object, requirements = {}) ⇒ Object
Finds and builds the next transition that can be performed on the given object.
Methods included from MatcherHelpers
Methods included from Assertions
#assert_exclusive_keys, #assert_valid_keys
Constructor Details
#initialize(machine, name, options = {}) ⇒ Event
Creates a new event within the context of the given machine
Configuration options:
-
:human_name- The human-readable version of this event’s name
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/state_machine/event.rb', line 42 def initialize(machine, name, = {}) #:nodoc: assert_valid_keys(, :human_name) @machine = machine @name = name @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name @human_name = [:human_name] || @name.to_s.tr('_', ' ') @guards = [] @known_states = [] add_actions end |
Instance Attribute Details
#guards ⇒ Object (readonly)
The list of guards that determine what state this event transitions objects to when fired
32 33 34 |
# File 'lib/state_machine/event.rb', line 32 def guards @guards end |
#human_name(klass = @machine.owner_class) ⇒ Object
Transforms the event name into a more human-readable format, such as “turn on” instead of “turn_on”
65 66 67 |
# File 'lib/state_machine/event.rb', line 65 def human_name(klass = @machine.owner_class) @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name end |
#known_states ⇒ Object (readonly)
A list of all of the states known to this event using the configured guards/transitions as the source
36 37 38 |
# File 'lib/state_machine/event.rb', line 36 def known_states @known_states end |
#machine ⇒ Object
The state machine for which this event is defined
19 20 21 |
# File 'lib/state_machine/event.rb', line 19 def machine @machine end |
#name ⇒ Object (readonly)
The name of the event
22 23 24 |
# File 'lib/state_machine/event.rb', line 22 def name @name end |
#qualified_name ⇒ Object (readonly)
The fully-qualified name of the event, scoped by the machine’s namespace
25 26 27 |
# File 'lib/state_machine/event.rb', line 25 def qualified_name @qualified_name end |
Instance Method Details
#can_fire?(object) ⇒ Boolean
Determines whether any transitions can be performed for this event based on the current state of the given object.
If the event can’t be fired, then this will return false, otherwise true.
174 175 176 |
# File 'lib/state_machine/event.rb', line 174 def can_fire?(object) !transition_for(object).nil? end |
#draw(graph) ⇒ Object
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.
219 220 221 222 |
# File 'lib/state_machine/event.rb', line 219 def draw(graph) valid_states = machine.states.by_priority.map {|state| state.name} guards.collect {|guard| guard.draw(graph, name, valid_states)}.flatten end |
#fire(object, *args) ⇒ Object
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.
203 204 205 206 207 208 209 210 211 212 |
# File 'lib/state_machine/event.rb', line 203 def fire(object, *args) machine.reset(object) if transition = transition_for(object) transition.perform(*args) else machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)]]) false end end |
#initialize_copy(orig) ⇒ Object
Creates a copy of this event in addition to the list of associated guards to prevent conflicts across events within a class hierarchy.
57 58 59 60 61 |
# File 'lib/state_machine/event.rb', line 57 def initialize_copy(orig) #:nodoc: super @guards = @guards.dup @known_states = @known_states.dup end |
#inspect ⇒ Object
Generates a nicely formatted description of this event’s contents.
For example,
event = StateMachine::Event.new(machine, :park)
event.transition all - :idling => :parked, :idling => same
event # => #<StateMachine::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
231 232 233 234 235 236 237 238 239 |
# File 'lib/state_machine/event.rb', line 231 def inspect transitions = guards.map do |guard| guard.state_requirements.map do |state_requirement| "#{state_requirement[:from].description} => #{state_requirement[:to].description}" end * ', ' end "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>" end |
#transition(options) ⇒ Object
Creates a new transition that determines what to change the current state to when this event fires.
Defining transitions
The options for a new transition uses the Hash syntax to map beginning states to ending states. For example,
transition :parked => :idling, :idling => :first_gear
In this case, when the event is fired, this transition will cause the state to be idling if it’s current state is parked or first_gear if it’s current state is idling.
To help define these implicit transitions, a set of helpers are available for slightly more complex matching:
-
all- Matches every state in the machine -
all - [:parked, :idling, ...]- Matches every state except those specified -
any- An alias forall(matches every state in the machine) -
same- Matches the same state being transitioned from
See StateMachine::MatcherHelpers for more information.
Examples:
transition all => nil # Transitions to nil regardless of the current state
transition all => :idling # Transitions to :idling regardless of the current state
transition all - [:idling, :first_gear] => :idling # Transitions every state but :idling and :first_gear to :idling
transition nil => :idling # Transitions to :idling from the nil state
transition :parked => :idling # Transitions to :idling if :parked
transition [:parked, :stalled] => :idling # Transitions to :idling if :parked or :stalled
transition :parked => same # Loops :parked back to :parked
transition [:parked, :stalled] => same # Loops either :parked or :stalled back to the same state
transition all - :parked => same # Loops every state but :parked back to the same state
Verbose transitions
Transitions can also be defined use an explicit set of deprecated configuration options:
-
:from- A state or array of states that can be transitioned from. If not specified, then the transition can occur for any state. -
:to- The state that’s being transitioned to. If not specified, then the transition will simply loop back (i.e. the state will not change). -
:except_from- A state or array of states that cannot be transitioned from.
Examples:
transition :to => nil
transition :to => :idling
transition :except_from => [:idling, :first_gear], :to => :idling
transition :from => nil, :to => :idling
transition :from => [:parked, :stalled], :to => :idling
transition :from => :parked
transition :from => [:parked, :stalled]
transition :except_from => :parked
Notice that the above examples are the verbose equivalent of the examples described initially.
Conditions
In addition to the state requirements for each transition, a condition can also be defined to help determine whether that transition is available. These options will work on both the normal and verbose syntax.
Configuration options:
-
:if- 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. -
:unless- 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.
Examples:
transition :parked => :idling, :if => :moving?
transition :parked => :idling, :unless => :stopped?
transition :from => :parked, :to => :idling, :if => :moving?
transition :from => :parked, :to => :idling, :unless => :stopped?
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.
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/state_machine/event.rb', line 158 def transition() raise ArgumentError, 'Must specify as least one transition requirement' if .empty? # Only a certain subset of explicit options are allowed for transition # requirements assert_valid_keys(, :from, :to, :except_from, :if, :unless) if (.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty? guards << guard = Guard.new(.merge(:on => name)) @known_states |= guard.known_states guard end |
#transition_for(object, requirements = {}) ⇒ Object
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.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/state_machine/event.rb', line 180 def transition_for(object, requirements = {}) requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from) guards.each do |guard| if match = guard.match(object, requirements) # Guard allows for the transition to occur from = requirements[:from] to = match[:to].values.empty? ? from : match[:to].values.first return Transition.new(object, machine, name, from, to, !custom_from_state) end end # No transition matched nil end |