class EnumStateMachine::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 = EnumStateMachine::Machine.new(Vehicle) context = EnumStateMachine::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/enum_state_machine/state_context.rb 70 def initialize(state) 71 @state = state 72 @machine = state.machine 73 74 state_name = state.name 75 machine_name = machine.name 76 @condition = lambda {|object| object.class.state_machine(machine_name).states.matches?(object, state_name)} 77 end
Public Instance Methods
Hooks in condition-merging to methods that don't exist in this module
# File lib/enum_state_machine/state_context.rb 104 def method_missing(*args, &block) 105 # Get the configuration 106 if args.last.is_a?(Hash) 107 options = args.last 108 else 109 args << options = {} 110 end 111 112 # Get any existing condition that may need to be merged 113 if_condition = options.delete(:if) 114 unless_condition = options.delete(:unless) 115 116 # Provide scope access to configuration in case the block is evaluated 117 # within the object instance 118 proxy = self 119 proxy_condition = @condition 120 121 # Replace the configuration condition with the one configured for this 122 # proxy, merging together any existing conditions 123 options[:if] = lambda do |*condition_args| 124 # Block may be executed within the context of the actual object, so 125 # it'll either be the first argument or the executing context 126 object = condition_args.first || self 127 128 proxy.evaluate_method(object, proxy_condition) && 129 Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} && 130 !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)} 131 end 132 133 # Evaluate the method on the owner class with the condition proxied 134 # through 135 machine.owner_class.send(*args, &block) 136 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 EnumStateMachine::Machine#transition
for a description of the possible configurations for defining transitions.
# File lib/enum_state_machine/state_context.rb 95 def transition(options) 96 assert_valid_keys(options, :from, :to, :on, :if, :unless) 97 raise ArgumentError, 'Must specify :on event' unless options[:on] 98 raise ArgumentError, 'Must specify either :to or :from state' unless !options[:to] ^ !options[:from] 99 100 machine.transition(options.merge(options[:to] ? {:from => state.name} : {:to => state.name})) 101 end