Class: StateMachines::StateContext
- Inherits:
- 
      Module
      
        - Object
- Module
- StateMachines::StateContext
 
- Includes:
- EvalHelpers
- Defined in:
- lib/state_machines/state_context.rb
Overview
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(, &block)
      validations << 
    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 = StateMachines::Machine.new(Vehicle)
context = StateMachines::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
Instance Attribute Summary collapse
- 
  
    
      #machine  ⇒ Object 
    
    
  
  
  
  
    
      readonly
    
    
  
  
  
  
  
  
    The state machine for which this context’s state is defined. 
- 
  
    
      #state  ⇒ Object 
    
    
  
  
  
  
    
      readonly
    
    
  
  
  
  
  
  
    The state that must be present in an object for this context to be active. 
Instance Method Summary collapse
- 
  
    
      #initialize(state)  ⇒ StateContext 
    
    
  
  
  
    constructor
  
  
  
  
  
  
  
    Creates a new context for the given state. 
- 
  
    
      #method_missing(*args)  ⇒ Object 
    
    
  
  
  
  
  
  
  
  
  
    Hooks in condition-merging to methods that don’t exist in this module. 
- 
  
    
      #transition(options)  ⇒ Object 
    
    
  
  
  
  
  
  
  
  
  
    Creates a new transition that determines what to change the current state to when an event fires from this state. 
Methods included from EvalHelpers
#evaluate_method, #evaluate_method_with_event_args
Constructor Details
#initialize(state) ⇒ StateContext
Creates a new context for the given state
| 66 67 68 69 70 71 72 73 | # File 'lib/state_machines/state_context.rb', line 66 def initialize(state) @state = state @machine = state.machine state_name = state.name machine_name = machine.name @condition = ->(object) { object.class.state_machine(machine_name).states.matches?(object, state_name) } end | 
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(*args) ⇒ Object
Hooks in condition-merging to methods that don’t exist in this module
| 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | # File 'lib/state_machines/state_context.rb', line 100 def method_missing(*args, &) # Get the configuration if args.last.is_a?(Hash) = args.last else args << = {} end # Get any existing condition that may need to be merged if_condition = .delete(:if) unless_condition = .delete(:unless) # Provide scope access to configuration in case the block is evaluated # within the object instance proxy = self proxy_condition = @condition # Replace the configuration condition with the one configured for this # proxy, merging together any existing conditions [:if] = lambda do |*condition_args| # Block may be executed within the context of the actual object, so # it'll either be the first argument or the executing context object = condition_args.first || self proxy.evaluate_method(object, proxy_condition) && Array(if_condition).all? { |condition| proxy.evaluate_method(object, condition) } && !Array(unless_condition).any? { |condition| proxy.evaluate_method(object, condition) } end # Evaluate the method on the owner class with the condition proxied # through machine.owner_class.send(*args, &) end | 
Instance Attribute Details
#machine ⇒ Object (readonly)
The state machine for which this context’s state is defined
| 60 61 62 | # File 'lib/state_machines/state_context.rb', line 60 def machine @machine end | 
#state ⇒ Object (readonly)
The state that must be present in an object for this context to be active
| 63 64 65 | # File 'lib/state_machines/state_context.rb', line 63 def state @state end | 
Instance Method Details
#transition(options) ⇒ Object
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 StateMachines::Machine#transition for a description of the possible configurations for defining transitions.
| 91 92 93 94 95 96 97 | # File 'lib/state_machines/state_context.rb', line 91 def transition() StateMachines::OptionsValidator.assert_valid_keys!(, :from, :to, :on, :if, :unless) raise ArgumentError, 'Must specify :on event' unless [:on] raise ArgumentError, 'Must specify either :to or :from state' unless ![:to] ^ ![:from] machine.transition(.merge([:to] ? { from: state.name } : { to: state.name })) end |