RubyGuides
Share this post!

How to Use State Machines in Ruby

Imagine a traffic light…

It can be red, green or yellow.

When it changes colors, the next color is based on the current one.

Traffic Light State Machine

Let’s say that this is the kind that makes a sound for blind people so they know when they can cross.

Now:

You’re writing the software for this thing.

How are you going to know what sound to play every time & what color should be next?

You could write some if statements like this:

if @light.state == "green"
  @light.play_green_sound
end

if @light.state == "green"
  @light.change_to_yellow
end

# ...

This state checking code would be all over the place!

How can we improve this?

If you apply Object-Oriented Design principles you’ll re-discover the State Design Pattern.

What is The State Design Pattern?

The State Design Pattern is one way to implement a state machine.

You’ll need 3 components:

  • A Context class, this class knows what the current state is
  • A State class, this class defines the methods that should be implemented by the individual states
  • One class for every state. These classes will inherit from the State class

In our traffic light example, the context is the TrafficLight itself.

And the states are Green, Red & Yellow.

Every state will know what to do.

The big benefit?

Every state knows itself so there is no need to check for the current state. This translates into less conditional statements which are often a source of complexity.

Traffic Light Implementation

Let’s see the code for an actual implementation of this pattern.

Here’s the TrafficLight:

class TrafficLight
  def initialize
    @state = nil
  end

  def next_state(klass = Green)
    @state = klass.new(self)

    @state.beep
    @state.start_timer
  end
end

Here’s the base State:

class State
  def initialize(light)
    @light = light
  end

  def beep
  end

  def next_state
  end

  def start_timer
  end
end

Yes, these 3 methods are empty.

It’s a common convention in other programming languages (like Java) to define this “interface”, but it’s not popular in Ruby.

This is here for demonstration purposes.

However, we still want to share the initialize method between all the states because all of them need the context (TrafficLight object) to signal a state change.

Now:

The 3 states look very similar to each other, so I’m going to show you the code for just one of them.

Here’s the Green state:

class Green < State
  def beep
    puts "Color is now green"
  end

  def next_state
    @light.next_state(Yellow)
  end

  def start_timer
    sleep 5; next_state
  end
end

Every state knows how & when to switch to the next.

AI Game Example

You can use a state machine to solve games that depend on the current state, like RubyWarrior.

In RubyWarrior you're given a player object & a board.

The goals are to:

  • Defeat all the enemies on the board
  • Reach the exit while keeping your HP above 0

You can make one move at a time & you have to make a good choice if you want to complete the level.

Looking at the current state helps you make that choice.

That's why a state machine is a good solution.

Here's an example:

class Attacking < State
  def play(warrior)
    warrior.attack!

    @player.set_state(Healing) unless enemy_found?(warrior)
  end
end

This is one of the states that our warrior can be in, when we don't have any enemies in sight we move into the Healing state to recover from battle damage.

Using The AASM Gem

If you want to keep track of the current state while making sure that the transitions are valid then you can use a state machine gem like AASM.

This gem is built around the idea of events (like pressing a light switch) that trigger transitions into other states.

Here's an example:

require 'aasm'

class Light
  include AASM

  aasm do
    state :on, :off

    event :switch do
      transitions :from => :on, :to => :off, :if => :on?
      transitions :from => :off, :to => :on, :if => :off?
    end
  end
end

How to use this class:

light = Light.new

p light.on?
# true

light.switch

p light.on?
# false

Using this state machine you can only transition to the "on" state if the current state is "off". You can also have a number of callbacks (before/after) to run specific code during state transitions.

These callbacks could include things like:

  • Sending an email
  • Logging the state change
  • Updating a live monitoring dashboard

In addition, AASM has the option to save the current state to a database using ActiveRecord.

Summary

You have learned about state machines, the state design pattern & the AASM gem! Keep learning now by subscribing to my Ruby newsletter (7000+ subscribers) so you don't miss new articles & subscriber-exclusive Ruby tips.

Now it's time to practice with these new ideas 🙂

Thanks for reading.

Leave a Comment:

5 comments
MaJeD BoJaN says last week

Regarding to AASM gem am using in projects those i working on enum or enumerize

enumerize :status, in: {
  pending:  1, # When user login first time will be pending
  active:   2, # After user confirm his mobile number will be active
  inactive: 3  # Admin can disable users and those uses will not be able to login or use the app
}, default: :pending, scope: true, predicates: true

so i can check the states of the object and get it based on locale

Reply
Jones A. says 6 days ago

Excellent and informative! Thanks for helping me to rediscover the State Pattern.

Reply
    Jesus Castello says 6 days ago

    Thanks for reading! 🙂

    Reply
Wayne says 5 days ago

Nice article, thanks! About traffic light implementation, I see you pass TrafficLight instance into State and also create State instance in TrafficLight, I think it’s circular dependency, any idea?

Reply
    Jesus Castello says 5 days ago

    I don’t think it should be a problem because the state is not accessing itself throught the TrafficLight. In fact, TrafficLight has no attribute accessors, so there is no way to access state (unless you use metaprogramming).

    Thanks for your comment! 🙂

    Reply
Add Your Reply