Class: StateMachine::Transition
- Inherits:
-
Object
- Object
- StateMachine::Transition
- Defined in:
- lib/state_machine/transition.rb
Overview
A transition represents a state change for a specific attribute.
Transitions consist of:
-
An event
-
A starting state
-
An ending state
Instance Attribute Summary collapse
-
#args ⇒ Object
The arguments passed in to the event that triggered the transition (does not include the
run_actionboolean argument if specified). -
#event ⇒ Object
readonly
The event that triggered the transition.
-
#from ⇒ Object
readonly
The original state value before the transition.
-
#from_name ⇒ Object
readonly
The original state name before the transition.
-
#machine ⇒ Object
readonly
The state machine for which this transition is defined.
-
#object ⇒ Object
readonly
The object being transitioned.
-
#qualified_event ⇒ Object
readonly
The fully-qualified name of the event that triggered the transition.
-
#qualified_from_name ⇒ Object
readonly
The original fully-qualified state name before transition.
-
#qualified_to_name ⇒ Object
readonly
The new fully-qualified state name after the transition.
-
#result ⇒ Object
readonly
The result of invoking the action associated with the machine.
-
#to ⇒ Object
readonly
The new state value after the transition.
-
#to_name ⇒ Object
readonly
The new state name after the transition.
Class Method Summary collapse
-
.perform(transitions, options = {}) ⇒ Object
Runs one or more transitions in parallel.
-
.perform_within_transaction(transitions, options = {}) ⇒ Object
Runs one or more transitions within a transaction.
Instance Method Summary collapse
-
#action ⇒ Object
The action that will be run when this transition is performed.
-
#after(result = nil, success = true) ⇒ Object
Runs the machine’s
aftercallbacks for this transition. -
#attribute ⇒ Object
The attribute which this transition’s machine is defined for.
-
#attributes ⇒ Object
A hash of all the core attributes defined for this transition with their names as keys and values of the attributes as values.
-
#before ⇒ Object
Runs the machine’s
beforecallbacks for this transition. -
#initialize(object, machine, event, from_name, to_name, read_state = true) ⇒ Transition
constructor
Creates a new, specific transition.
-
#inspect ⇒ Object
Generates a nicely formatted description of this transitions’s contents.
-
#loopback? ⇒ Boolean
Does this transition represent a loopback (i.e. the from and to state are the same).
-
#perform(*args) ⇒ Object
Runs the actual transition and any before/after callbacks associated with the transition.
-
#persist ⇒ Object
Transitions the current value of the state to that specified by the transition.
-
#reset ⇒ Object
Resets any tracking of which callbacks have already been run and whether the state has already been persisted.
-
#rollback ⇒ Object
Rolls back changes made to the object’s state via this transition.
-
#within_transaction ⇒ Object
Runs a block within a transaction for the object being transitioned.
Constructor Details
#initialize(object, machine, event, from_name, to_name, read_state = true) ⇒ Transition
Creates a new, specific transition
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/state_machine/transition.rb', line 128 def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc: @object = object @machine = machine @args = [] # Event information event = machine.events.fetch(event) @event = event.name @qualified_event = event.qualified_name # From state information from_state = machine.states.fetch(from_name) @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) @to = to_state.value @to_name = to_state.name @qualified_to_name = to_state.qualified_name end |
Instance Attribute Details
#args ⇒ Object
The arguments passed in to the event that triggered the transition (does not include the run_action boolean argument if specified)
122 123 124 |
# File 'lib/state_machine/transition.rb', line 122 def args @args end |
#event ⇒ Object (readonly)
The event that triggered the transition
97 98 99 |
# File 'lib/state_machine/transition.rb', line 97 def event @event end |
#from ⇒ Object (readonly)
The original state value before the transition
103 104 105 |
# File 'lib/state_machine/transition.rb', line 103 def from @from end |
#from_name ⇒ Object (readonly)
The original state name before the transition
106 107 108 |
# File 'lib/state_machine/transition.rb', line 106 def from_name @from_name end |
#machine ⇒ Object (readonly)
The state machine for which this transition is defined
94 95 96 |
# File 'lib/state_machine/transition.rb', line 94 def machine @machine end |
#object ⇒ Object (readonly)
The object being transitioned
91 92 93 |
# File 'lib/state_machine/transition.rb', line 91 def object @object end |
#qualified_event ⇒ Object (readonly)
The fully-qualified name of the event that triggered the transition
100 101 102 |
# File 'lib/state_machine/transition.rb', line 100 def qualified_event @qualified_event end |
#qualified_from_name ⇒ Object (readonly)
The original fully-qualified state name before transition
109 110 111 |
# File 'lib/state_machine/transition.rb', line 109 def qualified_from_name @qualified_from_name end |
#qualified_to_name ⇒ Object (readonly)
The new fully-qualified state name after the transition
118 119 120 |
# File 'lib/state_machine/transition.rb', line 118 def qualified_to_name @qualified_to_name end |
#result ⇒ Object (readonly)
The result of invoking the action associated with the machine
125 126 127 |
# File 'lib/state_machine/transition.rb', line 125 def result @result end |
#to ⇒ Object (readonly)
The new state value after the transition
112 113 114 |
# File 'lib/state_machine/transition.rb', line 112 def to @to end |
#to_name ⇒ Object (readonly)
The new state name after the transition
115 116 117 |
# File 'lib/state_machine/transition.rb', line 115 def to_name @to_name end |
Class Method Details
.perform(transitions, options = {}) ⇒ Object
Runs one or more transitions in parallel. All transitions will run through the following steps:
-
Before callbacks
-
Persist state
-
Invoke action
-
After callbacks (if configured)
-
Rollback (if action is unsuccessful)
Configuration options:
-
:action- Whether to run the action configured for each transition -
:after- Whether to run after callbacks
If a block is passed to this method, that block will be called instead of invoking each transition’s action.
28 29 30 31 32 33 34 35 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/state_machine/transition.rb', line 28 def perform(transitions, = {}) # Validate that the transitions are for separate machines / attributes attributes = transitions.map {|transition| transition.attribute}.uniq raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != transitions.length success = false # Run before callbacks. If any callback halts, then the entire chain # is halted for every transition. if transitions.all? {|transition| transition.before} # Persist the new state for each attribute transitions.each {|transition| transition.persist} # Run the actions associated with each machine begin results = {} success = if block_given? # Block was given: use the result for each transition result = yield transitions.each {|transition| results[transition.action] = result} !!result elsif [:action] == false # Skip the action true else # Run each transition's action (only once) object = transitions.first.object transitions.all? do |transition| action = transition.action action && !results.include?(action) ? results[action] = object.send(action) : true end end rescue Exception # Action failed: rollback transitions.each {|transition| transition.rollback} raise end # 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 [:after] == false && success # Rollback the transitions if the transaction was unsuccessful transitions.each {|transition| transition.rollback} unless success end success end |
.perform_within_transaction(transitions, options = {}) ⇒ Object
Runs one or more transitions within a transaction. See StateMachine::Transition.perform for more information.
80 81 82 83 84 85 86 87 |
# File 'lib/state_machine/transition.rb', line 80 def perform_within_transaction(transitions, = {}) success = false transitions.first.within_transaction do success = perform(transitions, ) end success end |
Instance Method Details
#action ⇒ Object
The action that will be run when this transition is performed
157 158 159 |
# File 'lib/state_machine/transition.rb', line 157 def action machine.action end |
#after(result = nil, success = true) ⇒ Object
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 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 :halt 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 should never halt the execution of a perform.
Example
class Vehicle
state_machine do
after_transition :on => :ignite, :do => lambda {|vehicle| ...}
event :ignite do
transition :parked => :idling
end
end
end
vehicle = Vehicle.new
transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
transition.after(true)
309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/state_machine/transition.rb', line 309 def after(result = nil, success = true) @result = result catch(:halt) do unless @after_run callback(:after, :success => success) @after_run = true end end true end |
#attribute ⇒ Object
The attribute which this transition’s machine is defined for
152 153 154 |
# File 'lib/state_machine/transition.rb', line 152 def attribute machine.attribute end |
#attributes ⇒ Object
A hash of all the core attributes defined for this transition with their names as keys and values of the attributes as values.
Example
machine = StateMachine.new(Vehicle)
transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
transition.attributes # => {:object => #<Vehicle:0xb7d60ea4>, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}
181 182 183 |
# File 'lib/state_machine/transition.rb', line 181 def attributes @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to} end |
#before ⇒ Object
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| ...}
end
end
vehicle = Vehicle.new
transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
transition.before
236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/state_machine/transition.rb', line 236 def before result = false catch(:halt) do unless @before_run callback(:before) @before_run = true end result = true end result end |
#inspect ⇒ Object
Generates a nicely formatted description of this transitions’s contents.
For example,
transition = StateMachine::Transition.new(object, machine, :ignite, :parked, :idling)
transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
363 364 365 |
# File 'lib/state_machine/transition.rb', line 363 def inspect "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>" end |
#loopback? ⇒ Boolean
Does this transition represent a loopback (i.e. the from and to state are the same)
Example
machine = StateMachine.new(Vehicle)
StateMachine::Transition.new(Vehicle.new, machine, :park, :parked, :parked).loopback? # => true
StateMachine::Transition.new(Vehicle.new, machine, :park, :idling, :parked).loopback? # => false
169 170 171 |
# File 'lib/state_machine/transition.rb', line 169 def loopback? from_name == to_name end |
#perform(*args) ⇒ Object
Runs the actual transition and any before/after callbacks associated with the transition. The action associated with the transition/machine can be skipped by passing in false.
Examples
class Vehicle
state_machine :action => :save do
...
end
end
vehicle = Vehicle.new
transition = StateMachine::Transition.new(vehicle, machine, :ignite, :parked, :idling)
transition.perform # => Runs the +save+ action after setting the state attribute
transition.perform(false) # => Only sets the state attribute
201 202 203 204 205 206 207 |
# File 'lib/state_machine/transition.rb', line 201 def perform(*args) run_action = [true, false].include?(args.last) ? args.pop : true self.args = args # Run the transition self.class.perform_within_transaction([self], :action => run_action) end |
#persist ⇒ Object
Transitions the current value of the state to that specified by the transition. Once the state is persisted, it cannot be persisted again until this transition is reset.
Example
class Vehicle
state_machine do
event :ignite do
transition :parked => :idling
end
end
end
vehicle = Vehicle.new
transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
transition.persist
vehicle.state # => 'idling'
270 271 272 273 274 275 |
# File 'lib/state_machine/transition.rb', line 270 def persist unless @persisted machine.write(object, :state, to) @persisted = true end end |
#reset ⇒ Object
Resets any tracking of which callbacks have already been run and whether the state has already been persisted
353 354 355 |
# File 'lib/state_machine/transition.rb', line 353 def reset @before_run = @persisted = @after_run = false end |
#rollback ⇒ Object
Rolls back changes made to the object’s state via this transition. This will revert the state back to the from value.
Example
class Vehicle
state_machine :initial => :parked do
event :ignite do
transition :parked => :idling
end
end
end
vehicle = Vehicle.new # => #<Vehicle:0xb7b7f568 @state="parked">
transition = StateMachine::Transition.new(vehicle, Vehicle.state_machine, :ignite, :parked, :idling)
# Persist the new state
vehicle.state # => "parked"
transition.persist
vehicle.state # => "idling"
# Roll back to the original state
transition.rollback
vehicle.state # => "parked"
346 347 348 349 |
# File 'lib/state_machine/transition.rb', line 346 def rollback reset machine.write(object, :state, from) end |
#within_transaction ⇒ Object
Runs a block within a transaction for the object being transitioned. By default, transactions are a no-op unless otherwise defined by the machine’s integration.
212 213 214 215 216 |
# File 'lib/state_machine/transition.rb', line 212 def within_transaction machine.within_transaction(object) do yield end end |