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
Whether this state’s value should be cached after being evaluated
The human-readable name for the state
Whether or not this state is the initial state to use for new objects
Whether or not this state is the initial state to use for new objects
The state machine for which this state is defined
A custom lambda block for determining whether a given value matches this state
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
The unique identifier for the state used in event and callback definitions
The fully-qualified identifier for the state, scoped by the machine’s namespace
The value that is written to a machine’s attribute when an object transitions into this state
Public Instance Methods
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 216 def call(object, method, method_missing = nil, *args, &block) 217 if machine.states.matches?(object, name) && context_method = methods[method.to_sym] 218 # Method is defined by the state: proxy it through 219 context_method.bind(object).call(*args, &block) 220 else 221 # Dispatch to the superclass since the object either isn't in this state 222 # or this state doesn't handle the method 223 method_missing.call if method_missing 224 end 225 end
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 186 def context(&block) 187 machine_name = machine.name 188 189 # Evaluate the method definitions 190 context = StateContext.new(self) 191 context.class_eval(&block) 192 context.instance_methods.each do |method| 193 methods[method.to_sym] = context.instance_method(method) 194 195 # Calls the method defined by the current state of the machine 196 context.class_eval <<-end_eval, __FILE__, __LINE__ + 1 197 remove_method :#{method} 198 def #{method}(*args, &block) 199 self.class.state_machine(#{machine_name.inspect}).states.fetch(#{name.inspect}).call(self, #{method.inspect}, lambda {super(*args, &block)}, *args, &block) 200 end 201 end_eval 202 end 203 204 # Include the context so that it can be bound to the owner class (the 205 # context is considered an ancestor, so it's allowed to be bound) 206 machine.owner_class.class_eval { include context } 207 208 context 209 end
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 (*)
Configuration options:
-
:human_name
- Whether to use this state’s human name in the description or just the internal name
# File lib/state_machine/state.rb 132 def description(options = {}) 133 label = options[:human_name] ? human_name : name 134 description = label ? label.to_s : label.inspect 135 description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s 136 description 137 end
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”.
Configuration options:
-
:human_name
- Whether to use the state’s human name for the node’s label that gets drawn on the graph
# File lib/state_machine/state.rb 238 def draw(graph, options = {}) 239 node = graph.add_nodes(name ? name.to_s : 'nil', 240 :label => description(options), 241 :width => '1', 242 :height => '1', 243 :shape => final? ? 'doublecircle' : 'ellipse' 244 ) 245 246 # Add open arrow for initial state 247 graph.add_edges(graph.add_nodes('starting_state', :shape => 'point'), node) if initial? 248 249 true 250 end
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
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
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 258 def inspect 259 attributes = [[:name, name], [:value, @value], [:initial, initial?], [:context, methods.keys]] 260 "#<#{self.class} #{attributes.map {|attr, value| "#{attr}=#{value.inspect}"} * ' '}>" 261 end
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 177 def matches?(other_value) 178 matcher ? matcher.call(other_value) : other_value == value 179 end
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 148 def value(eval = true) 149 if @value.is_a?(Proc) && eval 150 if cache_value? 151 @value = @value.call 152 machine.states.update(self) 153 @value 154 else 155 @value.call 156 end 157 else 158 @value 159 end 160 end
Private Instance Methods
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 271 def add_predicate 272 # Checks whether the current value matches this state 273 machine.define_helper(:instance, "#{qualified_name}?") do |machine, object| 274 machine.states.matches?(object, name) 275 end 276 end
Should the value be cached after it’s evaluated for the first time?
# File lib/state_machine/state.rb 265 def cache_value? 266 @cache 267 end