Adding a state machine to a Rails application

Posted in: Development

We recently implemented a state machine in the Content Publisher. While this isn't a change many people will notice, it does lay the foundation for further improvements which we hope will benefit our users.

This work also contributed to my ongoing education in computer science concepts and design patterns. Hopefully this post will share some of what I've learned.

Introduction to state machines

A state machine, or finite-state machine, is a computational model. It's designed to manage things which can be in one of several pre-defined states. Items can transition between states, but can only be in one state at a time.

A properly-implemented state machine can define:

  • what your states are
  • what things can do while they're in a certain state
  • when something is allowed to transition from one state to another
  • what happens when something transitions between states

A good example of what I mean by 'states' is what happens when you order a product online. Your order goes through several states: 'received', 'processing payment', 'packing', 'out for delivery', 'delivered', possibly even 'returned'.

While the state the order is in changes during the process, it will only ever be in one state at a time. The order can be in a 'packing' state or a 'delivered' state, but never both at the same time.

There are also rules about how the order moves between states – for example, your order won't move from 'delivered' back to 'packing'.

And different things can happen during the states. Maybe you can cancel your order when it's still in 'received' or 'processing payment', but not once it's in 'out for delivery'.

All these rules and processes are set through the state machine.

The state of states in the Content Publisher

States are a key part of the workflow in the Content Publisher. Content items can be in 3 states: 'draft', 'in review' and 'published'. All items start out in 'draft', but as our users work on them they progress on to

But we might want to add more in the future, like 'scheduled for publication' or 'archived'.

While the Content Publisher has states, and items can move between states, we don't manage these in the clearest way. The logic which controls which states items can be in is spread across the codebase, which makes it difficult to read or to modify.

We decided it was time to implement our own state machine.

State machines and Ruby

We could build a state machine ourselves, but there are so many popular state machine gems out there that we wanted to look at those first.

I started by checking Ruby Toolbox to see what the most popular state machine gems are. The top one is state_machine, but this is no longer maintained, making it a risky proposition.

The next most popular gem is AASM, short for 'acts as state machine'. AASM is well-maintained, with an active community and pretty good documentation – all important if we want to welcome it into our codebase.

I also looked into several other gems, including workflow and transitions. But AASM seemed the most promising, so I tested it out by building a prototype in a fresh Rails app.

AASM in action in our discovery prototype
AASM in action in our discovery prototype

The prototype confirmed that AASM ticked all our boxes. It was time to implement it properly.

Adding a state machine to the Content Publisher

The next step was to get AASM up and running in the Content Publisher to replace our existing functionality.

Implementing AASM was more complex here than in the prototype. This was mostly because we've been working on the Content Publisher for nearly two years and so had a lot of code to change.

This gave me a good opportunity to get more familiar with a broad range of our code – everything from role-based permissions to version control.

If you're thinking of using a state machine in a Rails application, I'd definitely recommend trying to put it in place as early as possible to minimise the amount of refactoring.

After many commits and an extensive review, we finally shipped to production on 16 January.

What comes next?

While our switch to AASM made no immediate difference to our end users, it does make our code better and more extensible.

We've now laid the foundation for adding new states or transitions, which could enable new features – like scheduling an item to publish later, or unpublishing a live item.

We'll be doing some more research into how we can improve our workflow, so keep an eye out for more changes in the coming months.

Posted in: Development

Responses

  • (we won't publish this)

Write a response

  • Thank you for this blog post. I found it really interesting and very clearly written. Thank you Iris.

    • Thanks Marianne, I'm glad you liked it! It was an interesting project to work on, but definitely not the most visible piece of work, so it's cool to hear other people find it interesting too.

  • Well done on getting it shipped! It's not obvious to see, but keeping the engine clean and running well is essential to maintaining the quality of the more obvious features.

    I'm glad it worked out 🙂

    • Cheers Phil. We've already been reaping the rewards as it's made adding unpublishing much quicker. Looking forward to shipping that soon as well!