class StateMachine::State

A state defines a value that an attribute can be in after being transitioned 0 or more times. States can represent a value of any type in Ruby, though the most common (and default) type is String.

In addition to defining the machine's value, a state can also define a behavioral context for an object when that object is in the state. See StateMachine::Machine#state for more information about how state-driven behavior can be utilized.

Attributes

cache[RW]

Whether this state's value should be cached after being evaluated

human_name[W]

The human-readable name for the state

initial[RW]

Whether or not this state is the initial state to use for new objects

initial?[RW]

Whether or not this state is the initial state to use for new objects

machine[RW]

The state machine for which this state is defined

matcher[RW]

A custom lambda block for determining whether a given value matches this state

methods[R]

Tracks all of the methods that have been defined for the machine's owner class when objects are in this state.

Maps :method_name => UnboundMethod

name[R]

The unique identifier for the state used in event and callback definitions

qualified_name[R]

The fully-qualified identifier for the state, scoped by the machine's namespace

value[W]

The value that is written to a machine's attribute when an object transitions into this state

Public Instance Methods

call(object, method, method_missing = nil, *args, &block) click to toggle source

Calls a method defined in this state's context on the given object. All arguments and any block will be passed into the method defined.

If the method has never been defined for this state, then a NoMethodError will be raised.

    # File lib/state_machine/state.rb
210 def call(object, method, method_missing = nil, *args, &block)
211   if machine.states.matches?(object, name) && context_method = methods[method.to_sym]
212     # Method is defined by the state: proxy it through
213     context_method.bind(object).call(*args, &block)
214   else
215     # Dispatch to the superclass since the object either isn't in this state
216     # or this state doesn't handle the method
217     method_missing.call if method_missing
218   end
219 end
context(&block) click to toggle source

Defines a context for the state which will be enabled on instances of the owner class when the machine is in this state.

This can be called multiple times. Each time a new context is created, a new module will be included in the owner class.

    # File lib/state_machine/state.rb
181     def context(&block)
182       machine_name = machine.name
183       
184       # Evaluate the method definitions
185       context = StateContext.new(self)
186       context.class_eval(&block)
187       context.instance_methods.each do |method|
188         methods[method.to_sym] = context.instance_method(method)
189         
190         # Calls the method defined by the current state of the machine
191         context.class_eval <<-end_eval, __FILE__, __LINE__ + 1
192           def #{method}(*args, &block)
193             self.class.state_machine(#{machine_name.inspect}).states.fetch(#{name.inspect}).call(self, #{method.inspect}, lambda {super(*args, &block)}, *args, &block)
194           end
195         end_eval
196       end
197       
198       # Include the context so that it can be bound to the owner class (the
199       # context is considered an ancestor, so it's allowed to be bound)
200       machine.owner_class.class_eval { include context }
201       
202       context
203     end
description() click to toggle source

Generates a human-readable description of this state's name / value:

For example,

State.new(machine, :parked).description                               # => "parked"
State.new(machine, :parked, :value => :parked).description            # => "parked"
State.new(machine, :parked, :value => nil).description                # => "parked (nil)"
State.new(machine, :parked, :value => 1).description                  # => "parked (1)"
State.new(machine, :parked, :value => lambda {Time.now}).description  # => "parked (*)
    # File lib/state_machine/state.rb
128 def description
129   description = name ? name.to_s : name.inspect
130   description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s
131   description
132 end
draw(graph) click to toggle source

Draws a representation of this state on the given machine. This will create a new node on the graph with the following properties:

  • label - The human-friendly description of the state.

  • width - The width of the node. Always 1.

  • height - The height of the node. Always 1.

  • shape - The actual shape of the node. If the state is a final state, then “doublecircle”, otherwise “ellipse”.

The actual node generated on the graph will be returned.

    # File lib/state_machine/state.rb
230 def draw(graph)
231   node = graph.add_node(name ? name.to_s : 'nil',
232     :label => description,
233     :width => '1',
234     :height => '1',
235     :shape => final? ? 'doublecircle' : 'ellipse'
236   )
237   
238   # Add open arrow for initial state
239   graph.add_edge(graph.add_node('starting_state', :shape => 'point'), node) if initial?
240   
241   node
242 end
final?() click to toggle source

Determines whether there are any states that can be transitioned to from this state. If there are none, then this state is considered final. Any objects in a final state will remain so forever given the current machine's definition.

    # File lib/state_machine/state.rb
103 def final?
104   !machine.events.any? do |event|
105     event.branches.any? do |branch|
106       branch.state_requirements.any? do |requirement|
107         requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name)
108       end
109     end
110   end
111 end
human_name(klass = @machine.owner_class) click to toggle source

Transforms the state name into a more human-readable format, such as “first gear” instead of “first_gear”

    # File lib/state_machine/state.rb
115 def human_name(klass = @machine.owner_class)
116   @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
117 end
inspect() click to toggle source

Generates a nicely formatted description of this state's contents.

For example,

state = StateMachine::State.new(machine, :parked, :value => 1, :initial => true)
state   # => #<StateMachine::State name=:parked value=1 initial=true context=[]>
    # File lib/state_machine/state.rb
250 def inspect
251   attributes = [[:name, name], [:value, @value], [:initial, initial?], [:context, methods.keys]]
252   "#<#{self.class} #{attributes.map {|attr, value| "#{attr}=#{value.inspect}"} * ' '}>"
253 end
matches?(other_value) click to toggle source

Determines whether this state matches the given value. If no matcher is configured, then this will check whether the values are equivalent. Otherwise, the matcher will determine the result.

For example,

# Without a matcher
state = State.new(machine, :parked, :value => 1)
state.matches?(1)           # => true
state.matches?(2)           # => false

# With a matcher
state = State.new(machine, :parked, :value => lambda {Time.now}, :if => lambda {|value| !value.nil?})
state.matches?(nil)         # => false
state.matches?(Time.now)    # => true
    # File lib/state_machine/state.rb
172 def matches?(other_value)
173   matcher ? matcher.call(other_value) : other_value == value
174 end
value(eval = true) click to toggle source

The value that represents this state. This will optionally evaluate the original block if it's a lambda block. Otherwise, the static value is returned.

For example,

State.new(machine, :parked, :value => 1).value                        # => 1
State.new(machine, :parked, :value => lambda {Time.now}).value        # => Tue Jan 01 00:00:00 UTC 2008
State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => <Proc:0xb6ea7ca0@...>
    # File lib/state_machine/state.rb
143 def value(eval = true)
144   if @value.is_a?(Proc) && eval
145     if cache_value?
146       @value = @value.call
147       machine.states.update(self)
148       @value
149     else
150       @value.call
151     end
152   else
153     @value
154   end
155 end

Private Instance Methods

add_predicate() click to toggle source

Adds a predicate method to the owner class so long as a name has actually been configured for the state

    # File lib/state_machine/state.rb
263 def add_predicate
264   # Checks whether the current value matches this state
265   machine.define_helper(:instance, "#{qualified_name}?") do |machine, object|
266     machine.states.matches?(object, name)
267   end
268 end
cache_value?() click to toggle source

Should the value be cached after it's evaluated for the first time?

    # File lib/state_machine/state.rb
257 def cache_value?
258   @cache
259 end