state_machine
Dynamic Definition
There may be cases where the definition of a state machine is dynamic so you can't know the possible states or events for a machine until runtime. For example, users in your application may need to manage the state machine of a project or a task. Further, transitions (and their associated states / events) might need to be defined dynamically by an external source like admins.
To define dynamically-generated state machines:
class Vehicle
attr_accessor :state
# Make sure the machine gets initialized so the initial state gets set properly
def initialize(*)
super
machine
end
# Replace this with an external source (like a db)
def transitions
[
{:parked => :idling, :on => :ignite},
{:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up}
# ...
]
end
# Create a state machine for this vehicle instance dynamically based on the
# transitions defined from the source above
def machine
vehicle = self
@machine ||= Machine.new(vehicle, :initial => :parked, :action => :save) do
vehicle.transitions.each {|attrs| transition(attrs)}
end
end
def save
# Save the state change...
true
end
end
# Generic class for building machines
class Machine
def self.new(object, *args, &block)
machine_class = Class.new
machine = machine_class.state_machine(*args, &block)
attribute = machine.attribute
action = machine.action
# Delegate attributes
machine_class.class_eval do
define_method(:definition) { machine }
define_method(attribute) { object.send(attribute) }
define_method("#{attribute}=") {|value| object.send("#{attribute}=", value) }
define_method(action) { object.send(action) } if action
end
machine_class.new
end
end
vehicle = Vehicle.new # => #<Vehicle:0xb708412c @state="parked" ...>
vehicle.state # => "parked"
vehicle.machine.ignite # => true
vehicle.machine.state # => "idling
vehicle.state # => "idling"
vehicle.machine.state_transitions # => [#<StateMachine::Transition ...>]
vehicle.machine.definition.states.keys # => :first_gear, :second_gear, :parked, :idling