SimpleStateMachine - a Simple Enum Based State Machine for Ruby
Why should you use a state machine?
A state machine is mainly used to indicate a specific status a certain object is in. A State is some kind of a description - usually a few words commonly stored as a string in the storage layer. State machine simplify the process of transition between certain states that an object is allowed to do, mechanizing the transition itself - transition, callbacks and persistence included.
You should be using a state machine when your code defines different behaviors for an object - based on the current status the object is in. For example: a simple example for a state machine is a blog post. A blog post can be saved as a “draft” - then it won’t be shown in the blog, and a “published” state which makes it available for display in the blog.
You’ll probably want to add some callback functionality for the state change, like sending out a tweet notifying the world of your new post when a post becomes “published”.
The state machine in the Ruby world: AASM
One of the most widely used state machine gems in the Ruby/Rails world is probably the AASM gem. AASM is a great gem that has one basic flaw - it requires the state column / attribute
to be of a
Using this gem your database will look something like this (a simple user table for example):
1 2 3 4 5 6 7 8
and to simplify your queries you may have scopes in your model to match the the available states:
1 2 3 4 5 6 7 8 9 10 11 12
Yes, this is simple and clear enough to be used on almost any application that requires a state machine functionality, but when your data size increases beyond a certain point the string type dependency can be a real PITA.
Storing states: String vs. Integer vs. Enum
You have 3 options when you choose your data storage type for a state column: String (VARCHAR), Integer and Enum (Not all databases are supported).
Storing strings is by far the easiest implementation you can choose. As shown earlier when you store states as strings the code is readable, the database output is readable and you can add and remove states pretty easily (as long as those new states do not exceed your string column length LIMIT bar). As far as indexing strings, MySQL string indexes are bigger comparing to integer column indexes and comparing strings is a little bit slower than comparing integers so traversing a string index performance can drop a little bit.
ENUM column type is available out of the box for MySQL:
1 2 3 4
And you can still query the ENUM column based on the string representation of the ENUM:
Adding an ENUM fields to a Postgres database is a little bit tricky and requires a custom defintion for the ENUM column type
While simple to create, maintaining an ENUM column has its own PITA:
- When you use an ENUM column, you’re technically moving data from where it belongs (in actual database fields), to somewhere it doesn’t (into the database metadata, specifically a column definition). This is different than putting constraints on the data which can achieve the same result.
- When you create an ENUM column, you basically say “this is never going to change”. but when it does you’ll need to
ALTER TABLEthat can take a serious amount of time on big tables.
- You’ll have to maintain the same list of available ENUM values for application level usage (e.g: a drop down) since you can’t easily extract a distict list of available ENUM values from the column itself.
- Aside from not being reusable on the application layer, ENUM members aren’t reusable even in other tables.
- Inserting an illegal value to an ENUM does not cause an error (like a constraint will do) - in most databases it will simply truncate the value to an empty string and insert the faulty data silently, yay!.
The great ancestor of everything, the integer type is the simplest of them all but carries an overweight of being completely unreadable and depends on conjunction with the application code to provide a textual representation for vogue number based values. It is pretty clear that
1 2 3 4 5 6 7
doesn’t really makes it clear on which status zidane is.
The SimpleStateMachine gem is now available at a alpha version and includes a very simple state machine implementation:
- State definition
- Transition setup
- Enum representation of states
SimpleStateMachine does not makes it necessary to use ActiveRecord or any kind of ORM, it is a ruby free implementation.
state enum indexes will be stored as integers in case you choose to use some ORM, and will be transformed back to strings.
The is still some work to be down, especially on the query interface so patches and pull requests are welcome.
Short, short example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27