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 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 313 def ==(other) 314 other.instance_of?(self.class) && 315 other.object == object && 316 other.machine == machine && 317 other.from_name == from_name && 318 other.to_name == to_name && 319 other.event == event 320 end
The action that will be run when this transition is performed
# File lib/state_machine/transition.rb 109 def action 110 machine.action 111 end
The attribute which this transition's machine is defined for
# File lib/state_machine/transition.rb 104 def attribute 105 machine.attribute 106 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 185 def attributes 186 @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to} 187 end
The event that triggered the transition
# File lib/state_machine/transition.rb 114 def event 115 @event.name 116 end
The state name before the transition
# File lib/state_machine/transition.rb 129 def from_name 130 @from_state.name 131 end
The human-readable name of the event that triggered the transition
# File lib/state_machine/transition.rb 124 def human_event 125 @event.human_name(@object.class) 126 end
The human-readable state name before the transition
# File lib/state_machine/transition.rb 139 def human_from_name 140 @from_state.human_name(@object.class) 141 end
The new human-readable state name after the transition
# File lib/state_machine/transition.rb 154 def human_to_name 155 @to_state.human_name(@object.class) 156 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 328 def inspect 329 "#<#{self.class} #{%w(attribute event from from_name to to_name).map {|attr| "#{attr}=#{send(attr).inspect}"} * ' '}>" 330 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 166 def loopback? 167 from_name == to_name 168 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 207 def perform(*args) 208 run_action = [true, false].include?(args.last) ? args.pop : true 209 self.args = args 210 211 # Run the transition 212 !!TransitionCollection.new([self], :actions => run_action).perform 213 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 268 def persist 269 unless @persisted 270 machine.write(object, :state, to) 271 @persisted = true 272 end 273 end
The fully-qualified name of the event that triggered the transition
# File lib/state_machine/transition.rb 119 def qualified_event 120 @event.qualified_name 121 end
The fully-qualified state name before the transition
# File lib/state_machine/transition.rb 134 def qualified_from_name 135 @from_state.qualified_name 136 end
The new fully-qualified state name after the transition
# File lib/state_machine/transition.rb 149 def qualified_to_name 150 @to_state.qualified_name 151 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 306 def reset 307 @before_run = @persisted = @after_run = false 308 @paused_block = nil 309 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 299 def rollback 300 reset 301 machine.write(object, :state, from) 302 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 235 def run_callbacks(options = {}, &block) 236 options = {:before => true, :after => true}.merge(options) 237 @success = false 238 239 halted = pausable { before(options[:after], &block) } if options[:before] 240 241 # After callbacks are only run if: 242 # * An around callback didn't halt after yielding 243 # * They're enabled or the run didn't succeed 244 after if !(@before_run && halted) && (options[:after] || !@success) 245 246 @before_run 247 end
The new state name after the transition
# File lib/state_machine/transition.rb 144 def to_name 145 @to_state.name 146 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 173 def transient? 174 @transient 175 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 218 def within_transaction 219 machine.within_transaction(object) do 220 yield 221 end 222 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 437 def after 438 unless @after_run 439 # First resume previously paused callbacks 440 if resume 441 catch(:halt) do 442 type = @success ? :after : :failure 443 machine.callbacks[type].each {|callback| callback.call(object, context, self)} 444 end 445 end 446 447 @after_run = true 448 end 449 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 392 def before(complete = true, index = 0, &block) 393 unless @before_run 394 while callback = machine.callbacks[:before][index] 395 index += 1 396 397 if callback.type == :around 398 # Around callback: need to handle recursively. Execution only gets 399 # paused if: 400 # * The block fails and the callback doesn't run on failures OR 401 # * The block succeeds, but after callbacks are disabled (in which 402 # case a continuation is stored for later execution) 403 return if catch(:cancel) do 404 callback.call(object, context, self) do 405 before(complete, index, &block) 406 407 pause if @success && !complete 408 throw :cancel, true unless @success 409 end 410 end 411 else 412 # Normal before callback 413 callback.call(object, context, self) 414 end 415 end 416 417 @before_run = true 418 end 419 420 action = {:success => true}.merge(block_given? ? yield : {}) 421 @result, @success = action[:result], action[:success] 422 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 459 def context 460 @context ||= {:on => event, :from => from_name, :to => to_name} 461 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 339 def pausable 340 begin 341 halted = !catch(:halt) { yield; true } 342 rescue Exception => error 343 raise unless @resume_block 344 end 345 346 if @resume_block 347 @resume_block.call(halted, error) 348 else 349 halted 350 end 351 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 356 def pause 357 raise ArgumentError, 'around_transition callbacks cannot be called in multiple execution contexts in java implementations of Ruby. Use before/after_transitions instead.' if RUBY_PLATFORM == 'java' 358 359 unless @resume_block 360 require 'continuation' unless defined?(callcc) 361 callcc do |block| 362 @paused_block = block 363 throw :halt, true 364 end 365 end 366 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 370 def resume 371 if @paused_block 372 halted, error = callcc do |block| 373 @resume_block = block 374 @paused_block.call 375 end 376 377 @resume_block = @paused_block = nil 378 379 raise error if error 380 !halted 381 else 382 true 383 end 384 end