Stop storing strings when you should use Enums

Damien Le Thiec
dlet
Published in
4 min readAug 21, 2017

A dive into one of the most interesting but surprisingly little known ActiveRecord feature.

A status problem

Let’s say you are building an e-commerce application in Rails. Of course, you will store orders in your database. These orders have different statuses. They can be pending (not paid yet), paid or canceled. How would you code this?

When I first faced this problem, I probably had the worst answer. I stored the status of my orders as strings. The result?

  • First, my code was clunky. You don’t want to write Order.where(status: "paid") everytime you need a specific set of orders. So you write helpers… But you need a lot of them… So you give up.
  • Second, it was unreliable. What if, in one of your controller, you make a typo and write order.update(status: "peding") ? Of course, you should catch this with your tests. But if not? Well, you are screwed and your data has just become distrustful.

There are several other ways to solve this problem. You could, for example store each status in your database as a boolean. You would have a paid column in your database that you would set to true or false. And the same with a pending and a canceled column. So ugly.

Today we gonna talk about what I think is the most efficient way to deal with this: ActiveRecord Enums.

Say hello to Rails magic

While a lot of people hate Rails magic, I love it. I love to be given ready-to-use, reliable and elegant solutions to solve my code problems. Enums is one of them. With Enums, you can map your attributes to integers in your database but query them by their real name. This can work for status columns but also for various other use cases.

  • Positions in a company (employee, manager, VP, etc.)
  • Phone types (office, mobile, home, etc.)
  • Gender (man or woman)
  • etc.

The list goes on and on. You can basically use Enums every time your attribute takes its value among a predefined set of options. Of course, you can also expand these options later.

Enums gives you the best of both worlds: the readability of strings and the reliability of integers. To help you understand, let’s write some code.

Quick setup…

To be able to use Enums, we will first need a rails migration. As we said, we will only store integers in our database.

class AddStatusesToOrders < ActiveRecord::Migration
def change
add_column :orders, :status, :integer, default: 0
end
end

Great ! Now, we need to tell ActiveRecord which status should correspond to each integer we store in our database. We do this in our model.

class Order < ActiveRecord::Base
enum status: [:pending, :paid, :canceled]
end

Please beware that the order of our array of symbols is really important here. It means that the order is pending if you store 0 as its status in the database, paid if you store 1, canceled if you store 2. If you change the order, the mapping change.

To avoid any mistake, you can also precisely map the integer values to the statuses like below.

class Order < ActiveRecord::Base
enum status: { paid: 1, pending: 0, canceled: 2 }
end

Unveiling the power of Enums

Now that we are done with the setup, we can begin to play with our Enum. First, go to your console and try to get the status of an order.

Instead of this:

irb(main):001:0> Order.first.status
# => 0

You should see this:

irb(main):002:0> Order.first.status
# => "pending"

So much easier to deal with for us as developers!

Enums also gives you access to a set of really useful methods and scopes. Let’s have a look!

  • Check if an instance has a specific status
irb(main):003:0> Order.first.paid?
# => false
irb(main):004:0> Order.first.pending?
# => true
  • Change the status of an instance
irb(main):005:0> Order.first.paid!
# => true
irb(main):006:0> Order.first.status
# => "paid"
  • Getting an ActiveRecord_Relation of orders with a specific status
irb(main):007:0> Order.paid
Order Load (29.9ms) SELECT "orders".* FROM "orders" WHERE "orders"."status" = $1 [["status", 1]]
# => An ActiveRecord_Relation containing our paid orders
  • Getting all the statuses of our orders (to use for a select field in a form for example)
irb(main):008:0> Order.statuses
# => { "pending" => 0, "paid" => 1, "canceled" => 2 }
irb(main):009:0> Order.statuses.keys
# => ["pending", "paid", "canceled"]

Going Further

Enums will really make your life easier. To learn more, below is a list of interesting articles and resources:

I let you with this tweet from DHH. Yes, I really love Rails magic!

--

--