Class: StateMachine::Transition

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(object, machine, event, from_name, to_name, read_state = true) ⇒ Transition

Creates a new, specific transition



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/state_machine/transition.rb', line 94

def initialize(object, machine, event, from_name, to_name, read_state = true) #:nodoc:
  @object = object
  @machine = machine
  @args = []
  @transient = false
  @resume_block = nil
  
  @event = machine.events.fetch(event)
  @from_state = machine.states.fetch(from_name)
  @from = read_state ? machine.read(object, :state) : @from_state.value
  @to_state = machine.states.fetch(to_name)
  @to = @to_state.value
  
  reset
end

Instance Attribute Details

#argsObject

The arguments passed in to the event that triggered the transition (does not include the run_action boolean argument if specified)



79
80
81
# File 'lib/state_machine/transition.rb', line 79

def args
  @args
end

#fromObject (readonly)

The original state value before the transition



72
73
74
# File 'lib/state_machine/transition.rb', line 72

def from
  @from
end

#machineObject (readonly)

The state machine for which this transition is defined



69
70
71
# File 'lib/state_machine/transition.rb', line 69

def machine
  @machine
end

#objectObject (readonly)

The object being transitioned



66
67
68
# File 'lib/state_machine/transition.rb', line 66

def object
  @object
end

#resultObject (readonly)

The result of invoking the action associated with the machine



82
83
84
# File 'lib/state_machine/transition.rb', line 82

def result
  @result
end

#toObject (readonly)

The new state value after the transition



75
76
77
# File 'lib/state_machine/transition.rb', line 75

def to
  @to
end

#transient=(value) ⇒ Object (writeonly)

Whether the transition is only existing temporarily for the object



85
86
87
# File 'lib/state_machine/transition.rb', line 85

def transient=(value)
  @transient = value
end

Class Method Details

.pause_supported?Boolean

Determines whether the curreny ruby implementation supports pausing and resuming transitions

Returns:

  • (Boolean)


89
90
91
# File 'lib/state_machine/transition.rb', line 89

def self.pause_supported?
  !defined?(RUBY_ENGINE) || %w(ruby maglev).include?(RUBY_ENGINE)
end

Instance Method Details

#==(other) ⇒ Object

Determines equality of transitions by testing whether the object, states, and event involved in the transition are equal



320
321
322
323
324
325
326
327
# File 'lib/state_machine/transition.rb', line 320

def ==(other)
  other.instance_of?(self.class) &&
  other.object == object &&
  other.machine == machine &&
  other.from_name == from_name &&
  other.to_name == to_name &&
  other.event == event
end

#actionObject

The action that will be run when this transition is performed



116
117
118
# File 'lib/state_machine/transition.rb', line 116

def action
  machine.action
end

#attributeObject

The attribute which this transition's machine is defined for



111
112
113
# File 'lib/state_machine/transition.rb', line 111

def attribute
  machine.attribute
end

#attributesObject

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'}


192
193
194
# File 'lib/state_machine/transition.rb', line 192

def attributes
  @attributes ||= {:object => object, :attribute => attribute, :event => event, :from => from, :to => to}
end

#eventObject

The event that triggered the transition



121
122
123
# File 'lib/state_machine/transition.rb', line 121

def event
  @event.name
end

#from_nameObject

The state name before the transition



136
137
138
# File 'lib/state_machine/transition.rb', line 136

def from_name
  @from_state.name
end

#human_eventObject

The human-readable name of the event that triggered the transition



131
132
133
# File 'lib/state_machine/transition.rb', line 131

def human_event
  @event.human_name(@object.class)
end

#human_from_nameObject

The human-readable state name before the transition



146
147
148
# File 'lib/state_machine/transition.rb', line 146

def human_from_name
  @from_state.human_name(@object.class)
end

#human_to_nameObject

The new human-readable state name after the transition



161
162
163
# File 'lib/state_machine/transition.rb', line 161

def human_to_name
  @to_state.human_name(@object.class)
end

#inspectObject

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>


335
336
337
# File 'lib/state_machine/transition.rb', line 335

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

Returns:

  • (Boolean)


173
174
175
# File 'lib/state_machine/transition.rb', line 173

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
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


214
215
216
217
218
219
220
# File 'lib/state_machine/transition.rb', line 214

def perform(*args)
  run_action = [true, false].include?(args.last) ? args.pop : true
  self.args = args
  
  # Run the transition
  !!TransitionCollection.new([self], :actions => run_action).perform
end

#persistObject

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'


275
276
277
278
279
280
# File 'lib/state_machine/transition.rb', line 275

def persist
  unless @persisted
    machine.write(object, :state, to)
    @persisted = true
  end
end

#qualified_eventObject

The fully-qualified name of the event that triggered the transition



126
127
128
# File 'lib/state_machine/transition.rb', line 126

def qualified_event
  @event.qualified_name
end

#qualified_from_nameObject

The fully-qualified state name before the transition



141
142
143
# File 'lib/state_machine/transition.rb', line 141

def qualified_from_name
  @from_state.qualified_name
end

#qualified_to_nameObject

The new fully-qualified state name after the transition



156
157
158
# File 'lib/state_machine/transition.rb', line 156

def qualified_to_name
  @to_state.qualified_name
end

#resetObject

Resets any tracking of which callbacks have already been run and whether the state has already been persisted



313
314
315
316
# File 'lib/state_machine/transition.rb', line 313

def reset
  @before_run = @persisted = @after_run = false
  @paused_block = nil
end

#rollbackObject

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"


306
307
308
309
# File 'lib/state_machine/transition.rb', line 306

def rollback
  reset
  machine.write(object, :state, from)
end

#run_callbacks(options = {}, &block) ⇒ Object

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 with after enabled. Default is true.

This will return true if all before callbacks gets executed. After callbacks will not have an effect on the result.



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/state_machine/transition.rb', line 242

def run_callbacks(options = {}, &block)
  options = {:before => true, :after => true}.merge(options)
  @success = false
  
  halted = pausable { before(options[:after], &block) } if options[:before]
  
  # After callbacks are only run if:
  # * An around callback didn't halt after yielding
  # * They're enabled or the run didn't succeed
  after if !(@before_run && halted) && (options[:after] || !@success)
  
  @before_run
end

#to_nameObject

The new state name after the transition



151
152
153
# File 'lib/state_machine/transition.rb', line 151

def to_name
  @to_state.name
end

#transient?Boolean

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.

Returns:

  • (Boolean)


180
181
182
# File 'lib/state_machine/transition.rb', line 180

def transient?
  @transient
end

#within_transactionObject

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.



225
226
227
228
229
# File 'lib/state_machine/transition.rb', line 225

def within_transaction
  machine.within_transaction(object) do
    yield
  end
end