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:
@state = nil
def next_state(klass = Green)
@state = klass.new(self)
Here’s the base State:
@light = light
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.
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
puts "Color is now green"
sleep 5; next_state
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
@player.set_state(Healing) unless enemy_found?(warrior)
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:
state :on, :off
event :switch do
transitions :from => :on, :to => :off, :if => :on?
transitions :from => :off, :to => :on, :if => :off?
How to use this class:
light = Light.new
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.
AASM Gem Video
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.
Regarding to AASM gem am using in projects those i working on enum or enumerize
so i can check the states of the object and get it based on locale