class StateMachine::StateContext
Represents a module which will get evaluated within the context of a state.
Class-level methods are proxied to the owner class, injecting a custom :if
condition along with method. This assumes that the method has support for a set of configuration options, including :if
. This condition will check that the object's state matches this context's state.
Instance-level methods are used to define state-driven behavior on the state's owner class.
Examples¶ ↑
class Vehicle class << self attr_accessor :validations def validate(options, &block) validations << options end end self.validations = [] attr_accessor :state, :simulate def moving? self.class.validations.all? {|validation| validation[:if].call(self)} end end
In the above class, a simple set of validation behaviors have been defined. Each validation consists of a configuration like so:
Vehicle.validate :unless => :simulate Vehicle.validate :if => lambda {|vehicle| ...}
In order to scope validations to a particular state context, the class-level validate
method can be invoked like so:
machine = StateMachine::Machine.new(Vehicle) context = StateMachine::StateContext.new(machine.state(:first_gear)) context.validate(:unless => :simulate) vehicle = Vehicle.new # => #<Vehicle:0xb7ce491c @simulate=nil, @state=nil> vehicle.moving? # => false vehicle.state = 'first_gear' vehicle.moving? # => true vehicle.simulate = true vehicle.moving? # => false
Attributes
The state machine for which this context's state is defined
The state that must be present in an object for this context to be active
Public Class Methods
Creates a new context for the given state
# File lib/state_machine/state_context.rb 66 def initialize(state) 67 @state = state 68 @machine = state.machine 69 70 state_name = state.name 71 machine_name = machine.name 72 @condition = lambda {|object| object.class.state_machine(machine_name).states.matches?(object, state_name)} 73 end
Public Instance Methods
Hooks in condition-merging to methods that don't exist in this module
# File lib/state_machine/state_context.rb 100 def method_missing(*args, &block) 101 # Get the configuration 102 if args.last.is_a?(Hash) 103 options = args.last 104 else 105 args << options = {} 106 end 107 108 # Get any existing condition that may need to be merged 109 if_condition = options.delete(:if) 110 unless_condition = options.delete(:unless) 111 112 # Provide scope access to configuration in case the block is evaluated 113 # within the object instance 114 proxy = self 115 proxy_condition = @condition 116 117 # Replace the configuration condition with the one configured for this 118 # proxy, merging together any existing conditions 119 options[:if] = lambda do |*args| 120 # Block may be executed within the context of the actual object, so 121 # it'll either be the first argument or the executing context 122 object = args.first || self 123 124 proxy.evaluate_method(object, proxy_condition) && 125 Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} && 126 !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)} 127 end 128 129 # Evaluate the method on the owner class with the condition proxied 130 # through 131 machine.owner_class.send(*args, &block) 132 end
Creates a new transition that determines what to change the current state to when an event fires from this state.
Since this transition is being defined within a state context, you do not need to specify the :from
option for the transition. For example:
state_machine do state :parked do transition :to => :idling, :on => [:ignite, :shift_up] # Transitions to :idling transition :from => [:idling, :parked], :on => :park, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off end end
See StateMachine::Machine#transition
for a description of the possible configurations for defining transitions.
# File lib/state_machine/state_context.rb 91 def transition(options) 92 assert_valid_keys(options, :from, :to, :on, :if, :unless) 93 raise ArgumentError, 'Must specify :on event' unless options[:on] 94 raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from] 95 96 machine.transition(options.merge(options[:to] ? {:from => state.name} : {:to => state.name})) 97 end