Class: StateMachines::Event
- Inherits:
-
Object
- Object
- StateMachines::Event
- Includes:
- MatcherHelpers
- Defined in:
- lib/state_machines/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 branches configured for the event.
Instance Attribute Summary collapse
-
#branches ⇒ Object
readonly
The list of branches 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 branches/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, requirements = {}) ⇒ Boolean
Determines whether any transitions can be performed for this event based on the current state of the given object.
-
#context ⇒ Object
Evaluates the given block within the context of this event.
- #draw(graph, options = {}, io = $stdout) ⇒ Object
-
#fire(object, *event_args) ⇒ Object
Attempts to perform the next available transition on the given object.
-
#initialize(machine, name, options = nil, human_name: nil, **extra_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 branches to prevent conflicts across events within a class hierarchy.
-
#inspect ⇒ Object
Generates a nicely formatted description of this event’s contents.
-
#on_failure(object, *args) ⇒ Object
Marks the object as invalid and runs any failure callbacks associated with this event.
-
#reset ⇒ Object
Resets back to the initial state of the event, with no branches / known states associated.
-
#transition(options) ⇒ Object
Creates a new transition that determines what to change the current state to when this event fires.
-
#transition_for(object, requirements = {}, *event_args) ⇒ Object
Finds and builds the next transition that can be performed on the given object.
Methods included from MatcherHelpers
Constructor Details
#initialize(machine, name, options = nil, human_name: nil, **extra_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
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/state_machines/event.rb', line 36 def initialize(machine, name, = nil, human_name: nil, **) # :nodoc: # Handle both old hash style and new kwargs style for backward compatibility if .is_a?(Hash) # Old style: initialize(machine, name, {human_name: 'Custom Name'}) StateMachines::OptionsValidator.assert_valid_keys!(, :human_name) human_name = [:human_name] else # New style: initialize(machine, name, human_name: 'Custom Name') raise ArgumentError, "Unexpected positional argument: #{.inspect}" unless .nil? StateMachines::OptionsValidator.assert_valid_keys!(, :human_name) unless .empty? end @machine = machine @name = name @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name @human_name = human_name || @name.to_s.tr('_', ' ') reset # Output a warning if another event has a conflicting qualified name if (conflict = machine.owner_class.state_machines.detect { |_other_name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name] }) _name, other_machine = conflict warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}" else add_actions end end |
Instance Attribute Details
#branches ⇒ Object (readonly)
The list of branches that determine what state this event transitions objects to when fired
26 27 28 |
# File 'lib/state_machines/event.rb', line 26 def branches @branches 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”
74 75 76 |
# File 'lib/state_machines/event.rb', line 74 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 branches/transitions as the source
30 31 32 |
# File 'lib/state_machines/event.rb', line 30 def known_states @known_states end |
#machine ⇒ Object
The state machine for which this event is defined
13 14 15 |
# File 'lib/state_machines/event.rb', line 13 def machine @machine end |
#name ⇒ Object (readonly)
The name of the event
16 17 18 |
# File 'lib/state_machines/event.rb', line 16 def name @name end |
#qualified_name ⇒ Object (readonly)
The fully-qualified name of the event, scoped by the machine’s namespace
19 20 21 |
# File 'lib/state_machines/event.rb', line 19 def qualified_name @qualified_name end |
Instance Method Details
#can_fire?(object, requirements = {}) ⇒ 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.
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.
120 121 122 |
# File 'lib/state_machines/event.rb', line 120 def can_fire?(object, requirements = {}) !transition_for(object, requirements).nil? end |
#context ⇒ Object
Evaluates the given block within the context of this event. This simply provides a DSL-like syntax for defining transitions.
80 81 82 |
# File 'lib/state_machines/event.rb', line 80 def context(&) instance_eval(&) end |
#draw(graph, options = {}, io = $stdout) ⇒ Object
197 198 199 |
# File 'lib/state_machines/event.rb', line 197 def draw(graph, = {}, io = $stdout) machine.renderer.draw_event(self, graph, , io) end |
#fire(object, *event_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 StateMachines::Transition#perform instance method.
166 167 168 169 170 171 172 173 174 175 |
# File 'lib/state_machines/event.rb', line 166 def fire(object, *event_args) machine.reset(object) if (transition = transition_for(object, {}, *event_args)) transition.perform(*event_args) else on_failure(object, *event_args) false end end |
#initialize_copy(orig) ⇒ Object
Creates a copy of this event in addition to the list of associated branches to prevent conflicts across events within a class hierarchy.
66 67 68 69 70 |
# File 'lib/state_machines/event.rb', line 66 def initialize_copy(orig) # :nodoc: super @branches = @branches.dup @known_states = @known_states.dup end |
#inspect ⇒ Object
Generates a nicely formatted description of this event’s contents.
For example,
event = StateMachines::Event.new(machine, :park)
event.transition all - :idling => :parked, :idling => same
event # => #<StateMachines::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
208 209 210 211 212 213 214 215 216 |
# File 'lib/state_machines/event.rb', line 208 def inspect transitions = branches.flat_map do |branch| branch.state_requirements.map do |state_requirement| "#{state_requirement[:from].description} => #{state_requirement[:to].description}" end end.join(', ') "#<#{self.class} name=#{name.inspect} transitions=[#{transitions}]>" end |
#on_failure(object, *args) ⇒ Object
Marks the object as invalid and runs any failure callbacks associated with this event. This should get called anytime this event fails to transition.
179 180 181 182 183 184 185 186 |
# File 'lib/state_machines/event.rb', line 179 def on_failure(object, *args) state = machine.states.match!(object) machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]]) transition = Transition.new(object, machine, name, state.name, state.name) transition.args = args if args.any? transition.run_callbacks(before: false) end |
#reset ⇒ Object
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.
192 193 194 195 |
# File 'lib/state_machines/event.rb', line 192 def reset @branches = [] @known_states = [] end |
#transition(options) ⇒ Object
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 StateMachines::Machine#transition for a description of the possible configurations for defining transitions.
100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/state_machines/event.rb', line 100 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 StateMachines::OptionsValidator.assert_valid_keys!(, :from, :to, :except_from, :except_to, :if, :unless) if (.keys - %i[from to on except_from except_to except_on if unless]).empty? branches << branch = Branch.new(.merge(on: name)) @known_states |= branch.known_states branch end |
#transition_for(object, requirements = {}, *event_args) ⇒ 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.
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.
Event arguments are passed to guard conditions if they accept multiple parameters.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/state_machines/event.rb', line 136 def transition_for(object, requirements = {}, *event_args) StateMachines::OptionsValidator.assert_valid_keys!(requirements, :from, :to, :guard) requirements[:from] = machine.states.match!(object).name unless (custom_from_state = requirements.include?(:from)) branches.each do |branch| next unless (match = branch.match(object, requirements, event_args)) # Branch allows for the transition to occur from = requirements[:from] to = if match[:to].is_a?(LoopbackMatcher) from else values = requirements.include?(:to) ? [requirements[:to]].flatten : [from] | machine.states.map { |state| state.name } match[:to].filter(values).first end return Transition.new(object, machine, name, from, to, !custom_from_state) end # No transition matched nil end |