class StateMachines::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 StateMachines::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
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_machines/state.rb, line 214 def call(object, method, *args, &block) options = args.last.is_a?(Hash) ? args.pop : {} options = {:method_name => method}.merge(options) state = machine.states.match!(object) if state == self && object.respond_to?(method) object.send(method, *args, &block) elsif method_missing = options[:method_missing] # Dispatch to the superclass since the object either isn't in this state # or this state doesn't handle the method begin method_missing.call rescue NoMethodError => ex if ex.name.to_s == options[:method_name].to_s && ex.args == args # No valid context for this method raise InvalidContext.new(object, "State #{state.name.inspect} for #{machine.name.inspect} is not a valid context for calling ##{options[:method_name]}") else raise end end end 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_machines/state.rb, line 176 def context(&block) # Include the context context = @context machine.owner_class.class_eval { include context } # Evaluate the method definitions and track which ones were added old_methods = context_methods context.class_eval(&block) new_methods = context_methods.to_a.select { |(name, method)| old_methods[name] != method } # Alias new methods so that the only execute when the object is in this state new_methods.each do |(method_name, method)| context_name = context_name_for(method_name) context.class_eval " alias_method :"#{context_name}", :#{method_name} def #{method_name}(*args, &block) state = self.class.state_machine(#{machine.name.inspect}).states.fetch(#{name.inspect}) options = {:method_missing => lambda {super(*args, &block)}, :method_name => #{method_name.inspect}} state.call(self, :"#{context_name}", *(args + [options]), &block) end ", __FILE__, __LINE__ + 1 end true end
The list of methods that have been defined in this state's context
# File lib/state_machines/state.rb, line 203 def context_methods @context.instance_methods.inject({}) do |methods, name| methods.merge(name.to_sym => @context.instance_method(name)) end 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_machines/state.rb, line 122 def description(options = {}) label = options[:human_name] ? human_name : name description = label ? label.to_s : label.inspect description << " (#{@value.is_a?(Proc) ? '*' : @value.inspect})" unless name.to_s == @value.to_s description end
# File lib/state_machines/state.rb, line 237 def draw(graph, options = {}) fail NotImplementedError 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_machines/state.rb, line 93 def final? !machine.events.any? do |event| event.branches.any? do |branch| branch.state_requirements.any? do |requirement| requirement[:from].matches?(name) && !requirement[:to].matches?(name, :from => name) end end end end
Transforms the state name into a more human-readable format, such as “first gear” instead of “first_gear”
# File lib/state_machines/state.rb, line 105 def human_name(klass = @machine.owner_class) @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name end
Generates a nicely formatted description of this state's contents.
For example,
state = StateMachines::State.new(machine, :parked, :value => 1, :initial => true) state # => #<StateMachines::State name=:parked value=1 initial=true context=[]>
# File lib/state_machines/state.rb, line 247 def inspect attributes = [[:name, name], [:value, @value], [:initial, initial?]] "#<#{self.class} #{attributes.map { |attr, value| "#{attr}=#{value.inspect}" } * ' '}>" 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_machines/state.rb, line 167 def matches?(other_value) matcher ? matcher.call(other_value) : other_value == value 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_machines/state.rb, line 138 def value(eval = true) if @value.is_a?(Proc) && eval if cache_value? @value = @value.call machine.states.update(self) @value else @value.call end else @value end 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_machines/state.rb, line 260 def add_predicate # Checks whether the current value matches this state machine.define_helper(:instance, "#{qualified_name}?") do |machine, object| machine.states.matches?(object, name) end end
Should the value be cached after it's evaluated for the first time?
# File lib/state_machines/state.rb, line 254 def cache_value? @cache end
Generates the name of the method containing the actual implementation
# File lib/state_machines/state.rb, line 268 def context_name_for(method) :"__#{machine.name}_#{name}_#{method}_#{@context.object_id}__" end