Class | StateMachine::State |
In: |
lib/state_machine/state.rb
|
Parent: | Object |
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.
initial | -> | initial? |
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 |
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 |
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, line 216 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, line 186 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 "def \#{method}(*args, &block)\nself.class.state_machine(\#{machine_name.inspect}).states.fetch(\#{name.inspect}).call(self, \#{method.inspect}, lambda {super(*args, &block)}, *args, &block)\nend\n", __FILE__, __LINE__ + 1 197: end 198: 199: # Include the context so that it can be bound to the owner class (the 200: # context is considered an ancestor, so it's allowed to be bound) 201: machine.owner_class.class_eval { include context } 202: 203: context 204: 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 (*)
# File lib/state_machine/state.rb, line 133 133: def description 134: description = name ? name.to_s : name.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:
The actual node generated on the graph will be returned.
# File lib/state_machine/state.rb, line 236 236: def draw(graph) 237: node = graph.add_node(name ? name.to_s : 'nil', 238: :label => description, 239: :width => '1', 240: :height => '1', 241: :shape => final? ? 'doublecircle' : 'ellipse' 242: ) 243: 244: # Add open arrow for initial state 245: graph.add_edge(graph.add_node('starting_state', :shape => 'point'), node) if initial? 246: 247: node 248: 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, line 108 108: def final? 109: !machine.events.any? do |event| 110: event.branches.any? do |branch| 111: branch.state_requirements.any? do |requirement| 112: requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name) 113: end 114: end 115: end 116: 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, line 120 120: def human_name(klass = @machine.owner_class) 121: @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name 122: 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, line 256 256: def inspect 257: attributes = [[:name, name], [:value, @value], [:initial, initial?], [:context, methods.keys]] 258: "#<#{self.class} #{attributes.map {|attr, value| "#{attr}=#{value.inspect}"} * ' '}>" 259: 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, line 177 177: def matches?(other_value) 178: matcher ? matcher.call(other_value) : other_value == value 179: end
Converts the name of this state to a string
# File lib/state_machine/state.rb, line 100 100: def name_to_s 101: name.to_s 102: 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, line 148 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