Chirb Statemachine Talk
Last night I presented the Ruby Statemachine Gem to the Chicago Ruby Users Group (Chirb) Below are links to download the slides and coding example used.
Finite State Machines and the Statemachine Ruby Gem (Slides):
Statemachine Exercises:
Understanding Statemachines, Part 4: Superstates 6
Superstates
Often in statemachines, duplication can arise. For example, the vending machine in our examples may need periodic repairs. It’s not certain which state the vending machine will be in when the repair man arrives. So all states should have a transition into the Repair Mode state.

Diagram 1 - Without Superstates
In this diagram, both the Waiting and Paid states have a transition to the Repair Mode invoked by the repair event. Duplication! We can dry this up by using the Superstate construct. See below:

Diagram 2 - With Superstates
Here we introduce the Operational superstate. Both the Waiting and Paid states are contained within the superstate which implies that they inherit all of the superstate’s transitions. That means we only need one transition into the Repair Mode state from the Operational superstate to achieve the same behavior as the solution in diagram 1.
One statemachine may have multiple superstates. And every superstate may contain other superstates. ie. Superstates can be nested.
History State
The solution in diagram 2 has an advantage over diagram 1. In diagram 1, once the repair man is done he triggers the operate event and the vending machine transitions into the Waiting event. This is unfortunate. Even if the vending machine was in the Paid state before the repair man came along, it will be in the Waiting state after he leaves. Shouldn’t it go back into the Paid state?
Superstates come with the history state which solves this problem. Every superstate will remember which state it is in before the superstate is exited. This memory is stored in a pseudo state called the history state. Transitions that end in the history state will recall the last active state of the superstate and enter it.
You can see the history state being use in diagram 2. In this solution, the history state allows the vending machine to return from a repair session into the same state it was in before, as though nothing happened at all.
Code
The following code builds the statemachine in diagram 2. Watch out for the _H. This is how the history state is denoted. If you have a superstate named foo, then it’s history state will be named foo_H.
require 'rubygems'
require 'statemachine'
vending_machine = Statemachine.build do
superstate :operational do
trans :waiting, :dollar, :paid
trans :paid, :selection, :waiting
trans :waiting, :selection, :waiting
trans :paid, :dollar, :paid
event :repair, :repair_mode, Proc.new { puts "Entering Repair Mode" }
end
trans :repair_mode, :operate, :operational_H, Proc.new { puts "Exiting Repair Mode" }
on_entry_of :waiting, Proc.new { puts "Entering Waiting State" }
on_entry_of :paid, Proc.new { puts "Entering Paid State" }
end
vending_machine.repair
vending_machine.operate
vending_machine.dollar
vending_machine.repair
vending_machine.operate
Output:
Entering Repair Mode Exiting Repair Mode Entering Waiting State Entering Paid State Entering Repair Mode Exiting Repair Mode Entering Paid State
Next we should cover pseudo states.
Understanding Statemachines, Part 3: Conditional Logic 1
Conditions
If you’re doing any significant amount of work with statmachines, you will most certainly encounter some conditional logic in your statemachines. Take our vending machine. When ever a coin is inserted, the invoked event will depend on whether the total amount of money inserted is sufficient to buy something. If enough money has been tendered, the display should suggest that the customer make a selection. If insufficient money has been inserted, the customer should be prompted to insert more.
Conditional logic can be accomplished by using entry actions. See the diagram below.

State Diagram with Conditional Logic
Starting in the Accept Money state, when a coin is inserted, the coin event is fired and the statemachine transitions into the Coin Inserted state. This is where it gets fun. Upon entering of the Coin Inserted state its entry event is invoked: count_amount_tendered. This method will count the money and invoke the not_paid_yet or paid event accordingly. This will cause the statemachine to transition into the appropriate state.
The Coin Inserted state is unique. You wouldn’t expect to find the statemachine in the Coin Inserted state for any reason except to make this decision. Once the decision is made, the state changes. States like this are called Decision States.
Code
require 'rubygems'
require 'statemachine'
class VendingMachineContext
attr_accessor :statemachine
def initialize
@amount_tendered = 0
end
def add_coin
@amount_tendered = @amount_tendered + 25
end
def count_amount_tendered
if @amount_tendered >= 100
@statemachine.paid
else
@statemachine.not_paid_yet
end
end
def prompt_money
puts "$.#{@amount_tendered}: more money please"
end
def prompt_selection
puts "please make a selection"
end
end
vending_machine = Statemachine.build do
trans :accept_money, :coin, :coin_inserted, :add_coin
state :coin_inserted do
event :not_paid_yet, :accept_money, :prompt_money
event :paid, :await_selection, :prompt_selection
on_entry :count_amount_tendered
end
context VendingMachineContext.new
end
vending_machine.context.statemachine = vending_machine
vending_machine.coin
vending_machine.coin
vending_machine.coin
vending_machine.coin
Output:
$.25: more money please $.50: more money please $.75: more money please please make a selection
Next lesson: Superstates
Understanding Statemachines, Part 4: Superstates
Older posts: 1 2