A transition represents a state change for a specific attribute.
Transitions consist of:
- An event
- A starting state
- An ending state
- action
- after
- attribute
- attributes
- before
- callback
- context
- inspect
- loopback?
- perform
- perform
- perform_within_transaction
- persist
- reset
- rollback
- within_transaction
| [RW] | args | The arguments passed in to the event that triggered the transition (does not include the run_action boolean argument if specified) |
| [R] | event | The event that triggered the transition |
| [R] | from | The original state value before the transition |
| [R] | from_name | The original state name before the transition |
| [R] | machine | The state machine for which this transition is defined |
| [R] | object | The object being transitioned |
| [R] | qualified_event | The fully-qualified name of the event that triggered the transition |
| [R] | qualified_from_name | The original fully-qualified state name before transition |
| [R] | qualified_to_name | The new fully-qualified state name after the transition |
| [R] | result | The result of invoking the action associated with the machine |
| [R] | to | The new state value after the transition |
| [R] | to_name | The new state name after the transition |
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.
[ show source ]
# File lib/state_machine/transition.rb, line 28
28: def perform(transitions, options = {})
29: # Validate that the transitions are for separate machines / attributes
30: attributes = transitions.map {|transition| transition.attribute}.uniq
31: raise ArgumentError, 'Cannot perform multiple transitions in parallel for the same state machine attribute' if attributes.length != transitions.length
32:
33: success = false
34:
35: # Run before callbacks. If any callback halts, then the entire chain
36: # is halted for every transition.
37: if transitions.all? {|transition| transition.before}
38: # Persist the new state for each attribute
39: transitions.each {|transition| transition.persist}
40:
41: # Run the actions associated with each machine
42: begin
43: results = {}
44: success =
45: if block_given?
46: # Block was given: use the result for each transition
47: result = yield
48: transitions.each {|transition| results[transition.action] = result}
49: !!result
50: elsif options[:action] == false
51: # Skip the action
52: true
53: else
54: # Run each transition's action (only once)
55: object = transitions.first.object
56: transitions.all? do |transition|
57: action = transition.action
58: action && !results.include?(action) ? results[action] = object.send(action) : true
59: end
60: end
61: rescue Exception
62: # Action failed: rollback
63: transitions.each {|transition| transition.rollback}
64: raise
65: end
66:
67: # Run after callbacks even when the actions failed. The :after option
68: # is ignored if the transitions were unsuccessful.
69: transitions.each {|transition| transition.after(results[transition.action], success)} unless options[:after] == false && success
70:
71: # Rollback the transitions if the transaction was unsuccessful
72: transitions.each {|transition| transition.rollback} unless success
73: end
74:
75: success
76: end
Runs one or more transitions within a transaction. See StateMachine::Transition.perform for more information.
[ show source ]
# File lib/state_machine/transition.rb, line 80
80: def perform_within_transaction(transitions, options = {})
81: success = false
82: transitions.first.within_transaction do
83: success = perform(transitions, options)
84: end
85:
86: success
87: end
The action that will be run when this transition is performed
[ show source ]
# File lib/state_machine/transition.rb, line 157
157: def action
158: machine.action
159: end
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)
[ show source ]
# File lib/state_machine/transition.rb, line 309
309: def after(result = nil, success = true)
310: @result = result
311:
312: catch(:halt) do
313: unless @after_run
314: callback(:after, :success => success)
315: @after_run = true
316: end
317: end
318:
319: true
320: end
The attribute which this transition‘s machine is defined for
[ show source ]
# File lib/state_machine/transition.rb, line 152
152: def attribute
153: machine.attribute
154: end
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'}
[ show source ]
# File lib/state_machine/transition.rb, line 181
181: def attributes
182: @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
183: end
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
[ show source ]
# File lib/state_machine/transition.rb, line 236
236: def before
237: result = false
238:
239: catch(:halt) do
240: unless @before_run
241: callback(:before)
242: @before_run = true
243: end
244:
245: result = true
246: end
247:
248: result
249: end
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>
[ show source ]
# File lib/state_machine/transition.rb, line 363
363: def inspect
364: "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>"
365: end
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
[ show source ]
# File lib/state_machine/transition.rb, line 169
169: def loopback?
170: from_name == to_name
171: end
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
[ show source ]
# File lib/state_machine/transition.rb, line 201
201: def perform(*args)
202: run_action = [true, false].include?(args.last) ? args.pop : true
203: self.args = args
204:
205: # Run the transition
206: self.class.perform_within_transaction([self], :action => run_action)
207: end
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'
[ show source ]
# File lib/state_machine/transition.rb, line 270
270: def persist
271: unless @persisted
272: machine.write(object, :state, to)
273: @persisted = true
274: end
275: end
Resets any tracking of which callbacks have already been run and whether the state has already been persisted
[ show source ]
# File lib/state_machine/transition.rb, line 353
353: def reset
354: @before_run = @persisted = @after_run = false
355: end
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"
[ show source ]
# File lib/state_machine/transition.rb, line 346
346: def rollback
347: reset
348: machine.write(object, :state, from)
349: end
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.
[ show source ]
# File lib/state_machine/transition.rb, line 212
212: def within_transaction
213: machine.within_transaction(object) do
214: yield
215: end
216: end
Runs the callbacks of the given type for this transition. This will only invoke callbacks that exactly match the event, from state, and to state that describe this transition.
Additional callback parameters can be specified. By default, this transition is also passed into callbacks.
[ show source ]
# File lib/state_machine/transition.rb, line 386
386: def callback(type, context = {})
387: context = self.context.merge(context)
388:
389: machine.callbacks[type].each do |callback|
390: callback.call(object, context, self)
391: end
392: end
Gets a hash of the context defining this unique transition (including event, from state, and to state).
Example
machine = StateMachine.new(Vehicle)
transition = StateMachine::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling)
transition.context # => {:on => :ignite, :from => :parked, :to => :idling}
[ show source ]
# File lib/state_machine/transition.rb, line 376
376: def context
377: @context ||= {:on => event, :from => from_name, :to => to_name}
378: end