class StateMachine::Transition
A transition represents a state change for a specific attribute.
Transitions consist of:
-
An event
-
A starting state
-
An ending state
Attributes
The arguments passed in to the event that triggered the transition (does not include the run_action
boolean argument if specified)
The original state value before the transition
The state machine for which this transition is defined
The object being transitioned
The result of invoking the action associated with the machine
The new state value after the transition
Whether the transition is only existing temporarily for the object
Public Class Methods
Determines whether the curreny ruby implementation supports pausing and resuming transitions
# File lib/state_machine/transition.rb 89 def self.pause_supported? 90 !defined?(RUBY_ENGINE) || %w(ruby maglev).include?(RUBY_ENGINE) 91 end
Public Instance Methods
Determines equality of transitions by testing whether the object, states, and event involved in the transition are equal
# File lib/state_machine/transition.rb 320 def ==(other) 321 other.instance_of?(self.class) && 322 other.object == object && 323 other.machine == machine && 324 other.from_name == from_name && 325 other.to_name == to_name && 326 other.event == event 327 end
The action that will be run when this transition is performed
# File lib/state_machine/transition.rb 116 def action 117 machine.action 118 end
The attribute which this transition’s machine is defined for
# File lib/state_machine/transition.rb 111 def attribute 112 machine.attribute 113 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'}
# File lib/state_machine/transition.rb 192 def attributes 193 @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to} 194 end
The event that triggered the transition
# File lib/state_machine/transition.rb 121 def event 122 @event.name 123 end
The state name before the transition
# File lib/state_machine/transition.rb 136 def from_name 137 @from_state.name 138 end
The human-readable name of the event that triggered the transition
# File lib/state_machine/transition.rb 131 def human_event 132 @event.human_name(@object.class) 133 end
The human-readable state name before the transition
# File lib/state_machine/transition.rb 146 def human_from_name 147 @from_state.human_name(@object.class) 148 end
The new human-readable state name after the transition
# File lib/state_machine/transition.rb 161 def human_to_name 162 @to_state.human_name(@object.class) 163 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>
# File lib/state_machine/transition.rb 335 def inspect 336 "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>" 337 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
# File lib/state_machine/transition.rb 173 def loopback? 174 from_name == to_name 175 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 transition.perform(Time.now) # => Passes in additional arguments and runs the +save+ action transition.perform(Time.now, false) # => Passes in additional arguments and only sets the state attribute
# File lib/state_machine/transition.rb 214 def perform(*args) 215 run_action = [true, false].include?(args.last) ? args.pop : true 216 self.args = args 217 218 # Run the transition 219 !!TransitionCollection.new([self], :actions => run_action).perform 220 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'
# File lib/state_machine/transition.rb 275 def persist 276 unless @persisted 277 machine.write(object, :state, to) 278 @persisted = true 279 end 280 end
The fully-qualified name of the event that triggered the transition
# File lib/state_machine/transition.rb 126 def qualified_event 127 @event.qualified_name 128 end
The fully-qualified state name before the transition
# File lib/state_machine/transition.rb 141 def qualified_from_name 142 @from_state.qualified_name 143 end
The new fully-qualified state name after the transition
# File lib/state_machine/transition.rb 156 def qualified_to_name 157 @to_state.qualified_name 158 end
Resets any tracking of which callbacks have already been run and whether the state has already been persisted
# File lib/state_machine/transition.rb 313 def reset 314 @before_run = @persisted = @after_run = false 315 @paused_block = nil 316 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"
# File lib/state_machine/transition.rb 306 def rollback 307 reset 308 machine.write(object, :state, from) 309 end
Runs the before / after callbacks for this transition. If a block is provided, then it will be executed between the before and after callbacks.
Configuration options:
-
before
- Whether to run before callbacks. -
after
- Whether to run after callbacks. If false, then any around callbacks will be paused until called again withafter
enabled. Default is true.
This will return true if all before callbacks gets executed. After callbacks will not have an effect on the result.
# File lib/state_machine/transition.rb 242 def run_callbacks(options = {}, &block) 243 options = {:before => true, :after => true}.merge(options) 244 @success = false 245 246 halted = pausable { before(options[:after], &block) } if options[:before] 247 248 # After callbacks are only run if: 249 # * An around callback didn't halt after yielding 250 # * They're enabled or the run didn't succeed 251 after if !(@before_run && halted) && (options[:after] || !@success) 252 253 @before_run 254 end
The new state name after the transition
# File lib/state_machine/transition.rb 151 def to_name 152 @to_state.name 153 end
Is this transition existing for a short period only? If this is set, it indicates that the transition (or the event backing it) should not be written to the object if it fails.
# File lib/state_machine/transition.rb 180 def transient? 181 @transient 182 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.
# File lib/state_machine/transition.rb 225 def within_transaction 226 machine.within_transaction(object) do 227 yield 228 end 229 end
Private Instance Methods
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.
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
.
# File lib/state_machine/transition.rb 444 def after 445 unless @after_run 446 # First resume previously paused callbacks 447 if resume 448 catch(:halt) do 449 type = @success ? :after : :failure 450 machine.callbacks[type].each {|callback| callback.call(object, context, self)} 451 end 452 end 453 454 @after_run = true 455 end 456 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.
# File lib/state_machine/transition.rb 399 def before(complete = true, index = 0, &block) 400 unless @before_run 401 while callback = machine.callbacks[:before][index] 402 index += 1 403 404 if callback.type == :around 405 # Around callback: need to handle recursively. Execution only gets 406 # paused if: 407 # * The block fails and the callback doesn't run on failures OR 408 # * The block succeeds, but after callbacks are disabled (in which 409 # case a continuation is stored for later execution) 410 return if catch(:cancel) do 411 callback.call(object, context, self) do 412 before(complete, index, &block) 413 414 pause if @success && !complete 415 throw :cancel, true unless @success 416 end 417 end 418 else 419 # Normal before callback 420 callback.call(object, context, self) 421 end 422 end 423 424 @before_run = true 425 end 426 427 action = {:success => true}.merge(block_given? ? yield : {}) 428 @result, @success = action[:result], action[:success] 429 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}
# File lib/state_machine/transition.rb 466 def context 467 @context ||= {:on => event, :from => from_name, :to => to_name} 468 end
Runs a block that may get paused. If the block doesn’t pause, then execution will continue as normal. If the block gets paused, then it will take care of switching the execution context when it’s resumed.
This will return true if the given block halts for a reason other than getting paused.
# File lib/state_machine/transition.rb 346 def pausable 347 begin 348 halted = !catch(:halt) { yield; true } 349 rescue Exception => error 350 raise unless @resume_block 351 end 352 353 if @resume_block 354 @resume_block.call(halted, error) 355 else 356 halted 357 end 358 end
Pauses the current callback execution. This should only occur within around callbacks when the remainder of the callback will be executed at a later point in time.
# File lib/state_machine/transition.rb 363 def pause 364 raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' unless self.class.pause_supported? 365 366 unless @resume_block 367 require 'continuation' unless defined?(callcc) 368 callcc do |block| 369 @paused_block = block 370 throw :halt, true 371 end 372 end 373 end
Resumes the execution of a previously paused callback execution. Once the paused callbacks complete, the current execution will continue.
# File lib/state_machine/transition.rb 377 def resume 378 if @paused_block 379 halted, error = callcc do |block| 380 @resume_block = block 381 @paused_block.call 382 end 383 384 @resume_block = @paused_block = nil 385 386 raise error if error 387 !halted 388 else 389 true 390 end 391 end