Ruby FSM
CHSM is a pretty nifty way to model finite state machines in Java or C++. It uses a DSL (Domain Specific Language) with embedded code blocks which is then compiled into the actual source. This FSM in Ruby is an attempt to model something very similar as a DSL.
The FSM described below exists in two levels. At the class level, it’s a set of events and states. The definitions of events and states use a DSL which makes the code incredibly readable and appears pseudo-declarative. At the instance level, the events are translated into methods on the defining class which can be invoked. Much in the spirit of CHSM, the only way to act upon the FSM is using the events which trigger state transitions. Depending on the state, a given event might trigger exceptions because it’s invalid for that state. Finally, you can trivially generate a GraphViz that visually shows the events and states.
Modeling a FSM
The core FSM class is modeled as a Ruby module which can be mixed into the enclosing class like so:
class Vending
include FSM
...
end
Events
Each event has a name and a handle method. The handle method returns true if the event is handled and initiates a state transition. The handle method ends up being an instance method on the enclosing FSM. Also notice that the state definition is effectively a class definition with setters and getters and internal structure.
class Vending
include FSM
event :coin do
attr_reader :value
handle do |args|
v = args[:value]
@value = [ 5, 10, 25 ].member?(v) ? v : nil
end
end
end
States
Each state can have optional enter and exit methods that are invoked when the state is entered or exited because of a transition. The first state defined in the FSM also becomes the start state. Transitions from the state are expressed using on and code blocks can be executed for the transitions as well. The state transitions are only taken based on successful event handlers, at which point, the transition code block is invoked. Multiple transitions may exist for a given state.
class Vending
...
attr_accessor :credit
state :idle do
enter do |fsm, ev|
fsm.credit = 0
end
on :coin => :collect
end
state :collect do
enter do |fsm, ev|
fsm.credit += ev.value
end
on :coin => :collect
on :select => :idle do |from, ev, to|
puts "you selected #{ev.value}"
end
end
end
Driving the FSM
As I mentioned before, the only way to interact with the FSM is using the methods that happen to have the same name as the events. Incorrect events when the FSM is in a particular state raise exceptions. The input which causes the events to get fired are typically generated from a socket, stdin or a user-interface.
v = Vending.new
v.start
v.coin :value => 5
v.coin :value => 10
v.select :item => :coke
Visualizing the FSM
Since the meta-data about the events and states are at the class level, querying these and building a dot file is pretty straight forward. Here’s the FSM visualized for the vending machine example:

Download FSM
The FSM is released under the BEER license and you can download it here: fsm-01tar.gz. MD5 is 7d8561d43822dae14fb001a71fd62ad6. There are a number of capabilities in CHSM that are not implemented here, specifically clusters. YMMV.
October 11th, 2008 at 7:03 pm
Wow, this looks fantastic! I was investigating FSM alternatives for a survey tool our hospital is building. This appears to be much more robust than the acts_as_state_machine plugin. I’ll try tinkering with it next week and report back. Thanks for your work!