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, &block) ⇒ 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
Constructor Details
#initialize(state) ⇒ StateContext
Creates a new context for the given state
63 64 65 66 67 68 69 70 |
# File 'lib/state_machines/state_context.rb', line 63 def initialize(state) @state = state @machine = state.machine state_name = state.name machine_name = machine.name @condition = lambda { |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, &block) ⇒ Object
Hooks in condition-merging to methods that don’t exist in this module
97 98 99 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 |
# File 'lib/state_machines/state_context.rb', line 97 def method_missing(*args, &block) # 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, &block) end |
Instance Attribute Details
#machine ⇒ Object (readonly)
The state machine for which this context’s state is defined
57 58 59 |
# File 'lib/state_machines/state_context.rb', line 57 def machine @machine end |
#state ⇒ Object (readonly)
The state that must be present in an object for this context to be active
60 61 62 |
# File 'lib/state_machines/state_context.rb', line 60 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.
88 89 90 91 92 93 94 |
# File 'lib/state_machines/state_context.rb', line 88 def transition() .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 |