Class | StateMachine::StateContext |
In: |
lib/state_machine/state_context.rb
|
Parent: | Module |
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.
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
machine | [R] | The state machine for which this context‘s state is defined |
state | [R] | The state that must be present in an object for this context to be active |
Creates a new context for the given state
# File lib/state_machine/state_context.rb, line 66 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
Hooks in condition-merging to methods that don‘t exist in this module
# File lib/state_machine/state_context.rb, line 100 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, line 91 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