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

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

Public Class Methods

new(state) click to toggle source

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

method_missing(*args, &block) click to toggle source

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
transition(options) click to toggle source

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