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.

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

Methods

Included Modules

Assertions EvalHelpers

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

Creates a new context for the given state

[Source]

    # 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

Public Instance methods

Hooks in condition-merging to methods that don‘t exist in this module

[Source]

     # File lib/state_machine/state_context.rb, line 99
 99:     def method_missing(*args, &block)
100:       # Get the configuration
101:       if args.last.is_a?(Hash)
102:         options = args.last
103:       else
104:         args << options = {}
105:       end
106:       
107:       # Get any existing condition that may need to be merged
108:       if_condition = options.delete(:if)
109:       unless_condition = options.delete(:unless)
110:       
111:       # Provide scope access to configuration in case the block is evaluated
112:       # within the object instance
113:       proxy = self
114:       proxy_condition = @condition
115:       
116:       # Replace the configuration condition with the one configured for this
117:       # proxy, merging together any existing conditions
118:       options[:if] = lambda do |*args|
119:         # Block may be executed within the context of the actual object, so
120:         # it'll either be the first argument or the executing context
121:         object = args.first || self
122:         
123:         proxy.evaluate_method(object, proxy_condition) &&
124:         Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} &&
125:         !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)}
126:       end
127:       
128:       # Evaluate the method on the owner class with the condition proxied
129:       # through
130:       machine.owner_class.send(*args, &block)
131:     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 => same, :on => :park, :unless => :seatbelt_on? # Transitions to :parked if seatbelt is off
     transition :to => :idling, :on => [:ignite, :shift_up]         # Transitions to :idling
   end
 end

See StateMachine::Machine#transition for a description of the possible configurations for defining transitions.

[Source]

    # File lib/state_machine/state_context.rb, line 91
91:     def transition(options)
92:       assert_valid_keys(options, :to, :on, :if, :unless)
93:       raise ArgumentError, 'Must specify :to state and :on event' unless options[:to] && options[:on]
94:       
95:       machine.transition(options.merge(:from => state.name))
96:     end

[Validate]