Maintainable State Machines Part 2 – don’t store state names in the database

December 3, 2010 Pivotal Labs

In relational databases it’s common to use foreign keys to reference other tables so that when you make a change to values in the table you maintain referential integrity without paying a performance cost. For some reason, many Rails programmers don’t apply this concept to state machines in ActiveRecord. In this post I’ll describe the benefits of not storing state names in the database.

Let’s say you have a community site that allows users to signup. Further, you need to verify their email addresses. You might start with a class like this:

class User
  state_machine :status do
    state :unverified
    state :verified
  end
end

Over time your community grows and some users stop going to the site. You decide that you are going to retire inactive accounts so that the usernames can be claimed by new users. You decide to add the concept of “active” and “inactive”. You update your class to look like this:

class User
  state_machine :status do
    state :unverified
    state :active
    state :inactive
  end
end

Unfortunately, you decided to store the states as strings in the database, which means that you need to run a migration to update existing users:

class AddActiveAndInactiveToUsers < ActiveRecord::Migration
  def self.up
    execute "update users set status = 'active' where status = 'verified';"
  end
end

But with 27 million users in the database, this query takes the site down for over 15 minutes so you have to schedule it for 3am. Even still, you get bad press for being down. And to think, all this can be avoided by just storing integers in the database!

Some state machine implementations provide easy ways to accomplish this. For example if you use PluginAWeek’s state_machine gem you could write:

class User
  state_machine :status do
    state :unverified, :value => 0
    state :verified, :value => 1
  end
end

Since you store integers in the database, you can easily add a new state and change the name of an existing state without having to migrate data. Having state names in the database is a classic example of dependency inversion, where your code, which doesn’t change very often, relies on a value in a database table, which is likely to change more often.

If your state machine doesn’t support storing values in the database as integers (or guids), I suggest you look into upgrading to one that does, or patching your existing state machine to support integers. You may end up getting one less sleepless night because of it!

About the Author

Biography

More Content by Pivotal Labs
Previous
Tips for writing testable, maintainable page-specific javascript
Tips for writing testable, maintainable page-specific javascript

I've worked on several large Rails apps and seen at least a dozen javascript systems. In this post I'll de...

Next
Maintainable State Machines Part 1 – scopes and predicate methods
Maintainable State Machines Part 1 – scopes and predicate methods

In this post I'll point out a few ways to write maintainable scopes and predicate methods on ActiveRecord o...