class EnumStateMachine::Event
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 branches configured for the event.
Attributes
The list of branches that determine what state this event transitions objects to when fired
The human-readable name for the event
A list of all of the states known to this event using the configured branches/transitions as the source
The state machine for which this event is defined
The name of the event
The fully-qualified name of the event, scoped by the machine's namespace
Public Instance Methods
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.
Note that this will not take the object context into account. Although a transition may be possible based on the state machine definition, object-specific behaviors (like validations) may prevent it from firing.
# File lib/enum_state_machine/event.rb 125 def can_fire?(object, requirements = {}) 126 !transition_for(object, requirements).nil? 127 end
Evaluates the given block within the context of this event. This simply provides a DSL-like syntax for defining transitions.
# File lib/enum_state_machine/event.rb 85 def context(&block) 86 instance_eval(&block) 87 end
Draws a representation of this event on the given graph. This will create 1 or more edges on the graph for each branch (i.e. transition) configured.
Configuration options:
-
:human_name
- Whether to use the event's human name for the node's label that gets drawn on the graph
# File lib/enum_state_machine/event.rb 205 def draw(graph, options = {}) 206 valid_states = machine.states.by_priority.map {|state| state.name} 207 branches.each do |branch| 208 branch.draw(graph, options[:human_name] ? human_name : name, valid_states) 209 end 210 211 true 212 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 EnumStateMachine::Transition#perform
instance method.
# File lib/enum_state_machine/event.rb 169 def fire(object, *args) 170 machine.reset(object) 171 172 if transition = transition_for(object) 173 transition.perform(*args) 174 else 175 on_failure(object) 176 false 177 end 178 end
Transforms the event name into a more human-readable format, such as “turn on” instead of “turn_on”
# File lib/enum_state_machine/event.rb 79 def human_name(klass = @machine.owner_class) 80 @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name 81 end
Generates a nicely formatted description of this event's contents.
For example,
event = EnumStateMachine::Event.new(machine, :park) event.transition all - :idling => :parked, :idling => same event # => #<EnumStateMachine::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
# File lib/enum_state_machine/event.rb 221 def inspect 222 transitions = branches.map do |branch| 223 branch.state_requirements.map do |state_requirement| 224 "#{state_requirement[:from].description} => #{state_requirement[:to].description}" 225 end * ', ' 226 end 227 228 "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>" 229 end
Marks the object as invalid and runs any failure callbacks associated with this event. This should get called anytime this event fails to transition.
# File lib/enum_state_machine/event.rb 182 def on_failure(object) 183 state = machine.states.match!(object) 184 machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]]) 185 186 Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false) 187 end
Resets back to the initial state of the event, with no branches / known states associated. This allows you to redefine an event in situations where you either are re-using an existing state machine implementation or are subclassing machines.
# File lib/enum_state_machine/event.rb 193 def reset 194 @branches = [] 195 @known_states = [] 196 end
Creates a new transition that determines what to change the current state to when this event fires.
Since this transition is being defined within an event context, you do not need to specify the :on
option for the transition. For example:
state_machine do event :ignite do transition :parked => :idling, :idling => same, :if => :seatbelt_on? # Transitions to :idling if seatbelt is on transition all => :parked, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off end end
See EnumStateMachine::Machine#transition
for a description of the possible configurations for defining transitions.
# File lib/enum_state_machine/event.rb 105 def transition(options) 106 raise ArgumentError, 'Must specify as least one transition requirement' if options.empty? 107 108 # Only a certain subset of explicit options are allowed for transition 109 # requirements 110 assert_valid_keys(options, :from, :to, :except_from, :except_to, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty? 111 112 branches << branch = Branch.new(options.merge(:on => name)) 113 @known_states |= branch.known_states 114 branch 115 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.
Valid requirement options:
-
:from
- One or more states being transitioned from. If none are specified, then this will be the object's current state. -
:to
- One or more states being transitioned to. If none are specified, then this will match any to state. -
:guard
- Whether to guard transitions with the if/unless conditionals defined for each one. Default is true.
# File lib/enum_state_machine/event.rb 139 def transition_for(object, requirements = {}) 140 assert_valid_keys(requirements, :from, :to, :guard) 141 requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from) 142 143 branches.each do |branch| 144 if match = branch.match(object, requirements) 145 # Branch allows for the transition to occur 146 from = requirements[:from] 147 to = if match[:to].is_a?(LoopbackMatcher) 148 from 149 else 150 values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.map {|state| state.name} 151 152 match[:to].filter(values).first 153 end 154 155 return Transition.new(object, machine, name, from, to, !custom_from_state) 156 end 157 end 158 159 # No transition matched 160 nil 161 end
Protected Instance Methods
Add the various instance methods that can transition the object using the current event
# File lib/enum_state_machine/event.rb 234 def add_actions 235 # Checks whether the event can be fired on the current object 236 machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args| 237 machine.event(name).can_fire?(object, *args) 238 end 239 240 # Gets the next transition that would be performed if the event were 241 # fired now 242 machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args| 243 machine.event(name).transition_for(object, *args) 244 end 245 246 # Fires the event 247 machine.define_helper(:instance, qualified_name) do |machine, object, *args| 248 machine.event(name).fire(object, *args) 249 end 250 251 # Fires the event, raising an exception if it fails 252 machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args| 253 object.send(qualified_name, *args) || raise(EnumStateMachine::InvalidTransition.new(object, machine, name)) 254 end 255 end