State machines in Rails 5

I often used to think in my college days, why a subject called Automata was there in our curriculum. At that time a book full of Moore & Mealy machines, DFA, Context Free Languages, Turing machines never amused me. But later I realised that in our daily life we are surrounded by State machines aka Finite State Machines (FSM). Whether we are withdrawing money from ATMs, or buying a nice pair of shoes from our favourite e-commerce website, or following traffic lights on road, they are everywhere.

Finite State Machine — a light intro

As the name says, it’s a way to model the behaviour of certain kinds of systems with some known number of states. I’ve always found it easier to understand with diagrams.

Lightbulb as a state machine

The diagram shown above is a simple example of a light bulb as a state machine. Here the bulb has two states: ON and OFF. Different events or inputs from switch may force it to change it’s state.

Now the lightbulb example may seem very dumb, but other real life state machines could be way more complex. So the very next question is…

Do we need it in Rails? — we will see

Well, you can obviously design your models without using a state machine. You can just write few more validations, instance and helper methods to make things work. It’s all right to do that as long as your model is limited to few states. But for example, think about writing the same for a complex checkout process of an e-commerce website. From stock checking at inventory to payment processing to order fulfilment, you will end up with writing a fat model which will contain only hundreds of lines validations for every state transition. And things will become worse when you have to add one more state in your model. Then you have to scamper through your codes only to ensure that adding a state will not break your system.

This is the main reason for using a state machine. It will simplify the design process and you can figure out every corner cases by simply drawing it out on paper.

Who else is using it?

I’ve seen FSM in action in a very popular open-source project, Spree Commerce. Spree is using it for their checkout, payment and order management.

Available options & my pick

We can build a state machine ourselves, but there are so many popular ruby gems out there for a long time with useful features. Lets have a look at them first.

state_machine: The most downloaded gem in this category. But unfortunately this feature packed option is no longer maintained, which makes it a risky proposition. I’ve tried this one first but it’s breaking in many places with latest Rails versions.

aasm: AASM aka ‘acts_as_state_machine’ is the second most popular candidate in the list as per the download count goes. AASM is regularly maintained by an active community and comes with a very nice documentation.

I’ve also checked few other gems like workflow and transitions, but AASM seems to be more promising to me. And it works out of the box with ActiveRecord and Mongoid.

Getting Started — with the plan

The plan is to simply add FSM behaviour to a Rails model with AASM. And this time we are not going to implement a simple lightbulb. Rather we will develop a FSM with more states, events and transitions.

As we know, every feature in a software is based around a flow of events. For example, if we consider about ‘listing a verified property’ on an real estate application like magicbricks.com or housing.com, the basic flow of events will be as follows.

  • Seller uploads a property with all legal documents and provides necessary details for listing.
  • Admin user validates those documents and she/he either marks the property as verified or rejects it.
  • Even after successful verification if admin thinks that some more details is required, then she/he will inform the seller and mark the property as unverified.
  • Post verification, admin user can publish and un-publish that property.
  • Once the property gets sold, admin moves the property into an archive.
Too much text!!!

Then lets have a look at the diagram below.

Different state transitions of Property model

Implementation —where magic happens

Now we will design our Property model as per the AASM documentation.

Installation

  1. First we have to include the gem in our Gemfile.
gem 'aasm', '~> 4.12'

2. Then we will add a string column aasm_state in properties table. By the way, the column name is configurable and I’ll talk about it later.

class AddAasmStateToProperties < ActiveRecord::Migration[5.1]
def change
add_column :properties, :aasm_state, :string
end
end

Basic Usage

In the Property model, first we need to include the AASM module.

class Property < ApplicationRecord
include AASM
...
end

Then inside aasm block we’ve to specify different states. As per the state diagram, whenever we will create a new instance of Property, it will always come with unverified initial state.

class Property < ApplicationRecord
include AASM
  aasm do
state :unverified, initial: true
state :verified
state :rejected
state :published
state :archived
end
end

Note: In this case we are using default column (aasm_state) for storing current state. But you easily override this behaviour inside aasm block.

class Property < ApplicationRecord
...
aasm column: 'state' do
...
end
end

Next we’ve to define all possible events. Each event can have a transition from one state to another.

class Property < ApplicationRecord
include AASM
  aasm do
state :unverified, initial: true
state :verified
state :published
state :archived
state :rejected
    event :verify do
transitions from: [:unverified], to: :verified
end
    event :reject do
transitions from: [:unverified], to: :rejected
end
    event :reverify do
transitions from: [:verified], to: :unverified
end
    event :publish do
transitions from: [:verified], to: :published
end
    event :unpublish do
transitions from: [:published], to: :verified
end
    event :archive do
transitions from: [:published, :verified, :unverified], to: :archived
end
end
end

Whoa! Just now we’ve supercharged our Property model with AASM. I think the code is very much self explanatory and you can easily figure out the flow.

Now there is a lot more we can do with this gem. It provides you with a couple of public methods for instances of the class Property. Let me show you some of them in rails console.

> p = Property.new # new property object with unverified state
> p.aasm_state # return the current state
=> "unverified"
> p.verified?
=> false
> p.may_verify?
=> true
> p.may_publish?
=> false
> p.verify # Changes the state to 'verified' but doesn't save it.
=> true
> p.verify! # => raises AASM::InvalidTransition
> p.publish! # Changes the state to 'published' and saves it.
=> true

Methods like verified? or may_publish? can be helpful in views to show some specific UI based on the current state.

Note: If you don’t like exceptions and prefer a simple true or false as response, you can tell AASM to do so:

class Property < ApplicationRecord
...
aasm :whiny_transitions => false do
...
end
end

Using Callbacks & Guards

This gem provides a number of callbacks which you can add directly to the states, events and transitions. And it will trigger the method whenever the callback happens.

class Property < ApplicationRecord
...
aasm do
state :unverified, initial: true, :before_enter => :do_something
...

after_all_transitions :log_status_change
    event :verify, :after => :notify_seller do
transitions from: [:unverified], to: :verified
end
    ....
  end

# Callback on state
def do_something
...
end
  # Callback on event
def notify_seller
...
end
  # Callback on transition
def log_status_change
puts "changing from #{aasm.from_state} to #{aasm.to_state}"
end
  ....
end

Here you can see a list of available callbacks, and their order of execution.

One important point to note is that our state machine doesn’t really care what the callback method returns. However you can add a guard class to any specific transition so that it won’t take place if the method returns false.

class Property < ApplicationRecord
...
aasm do
...
event :verify do
transitions from: [:unverified], to: :verified, :guard => :documents_checked?
end
....
end
  def documents_checked?
# If returned false, the execution will stop
end
end

You can use more than one guard per transition, which all have to succeed to proceed. Here you can check the official documentation for implementation variations.

Wrap Up

Managing states with AASM is not going to make any major difference to our end users, but it will make our code more cleaner, readable and extensible. There are more customisation options that AASM packs under the hood, read their official documentation for more information. You can find all source codes of this article in this Github repository.


Thanks for reading!!
If you have any questions or suggestions regarding this article, feel free to comment down below. Peace!