Creating Easy, Readable Attributes With ActiveRecord Enums

Imagine a question that can be either “pending”, “approved”, or “flagged”. Or a phone number that’s a “home”, “office”, “mobile”, or “fax” (if it’s 1982).

Some models call for this kind of data. An attribute that can have only one of a few different values. And that set of values almost never changes.

It’s a situation where, if it were plain Ruby, you’d just use a symbol.

You could create a PhoneNumberType or QuestionStatus model and a belongs_to relationship to hold these values, but that doesn’t seem worth it. You could stuff them in a yaml file, but now you have to look in a totally different place to figure out what your object can do.

In 4.1, Rails took a stab at solving this problem with ActiveRecord enums.

A few values, in the model

ActiveRecord enums are pretty easy. You give your model an integer column:

bin/rails g model phone number:string phone_number_type:integer

List the values that attribute can take:

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: [:home, :office, :mobile, :fax]
end

And now you can deal with strings instead of numbers.

Instead of this:

irb(main):001:0> Phone.first.phone_number_type
=> 3

You’ll see this:

irb(main):002:0> Phone.first.phone_number_type
=> "fax"

You can change that attribute using either strings or ints:

irb(main):003:0> phone.phone_number_type = 1; phone.phone_number_type
=> "office"
irb(main):004:0> phone.phone_number_type = "mobile"; phone.phone_number_type
=> "mobile"

Or even using a bang method:

irb(main):005:0> phone.office!
=> true
irb(main):006:0> phone.phone_number_type
=> "office"

You get methods for asking if your attribute has some specific value:

irb(main):007:0> phone.office?
=> true

And you can find all objects with the value you’re looking for:

irb(main):008:0> Phone.office
  Phone Load (0.3ms)  SELECT "phones".* FROM "phones" WHERE "phones"."phone_number_type" = ?  [["phone_number_type", 1]]

If you want to see all the different values you can use, along with the numbers they’re associated with, use the phone_number_types class method:

irb(main):009:0> Phone.phone_number_types
=> {"home"=>0, "office"=>1, "mobile"=>2, "fax"=>3}

Which makes them easy to put into an HTML form:

app/views/phones/_form.html.erb
<div class="field">
  <%= f.label :phone_number_type %><br>
  <%= f.select :phone_number_type, Phone.phone_number_types.keys %>
</div>

An enum in a form

A few things to watch for

Enums aren’t without their problems, though. You have to keep a few things in mind if you don’t want to run into trouble later on.

When you define an enum, order matters. So if you go back to your code and decide that those values should really be in alphabetical order:

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: [:fax, :home, :mobile, :office]
end

Your phones won’t have the right types anymore. You can get around this by telling enum which number goes with which value:

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: {fax: 3, home: 0, mobile: 2, office: 1}
end

But really, your best option is to keep the order consistent.

A bigger problem is what to do outside the Rails world. Even though Rails sees these enum values as strings, they’re just numbers inside your database. So someone looking at your raw data will have no idea what those numbers mean. This also means that every app that reads that database will have to know that enum mapping.

You could dump your enum mapping to the database or a yaml file if you really needed other people to see them. But that’s not DRY, because now you’re defining your enum in two places. And if you’re going that far, it might be better to do what we were avoiding in the beginning: create a totally separate model and association, so that a Phone would belong_to a PhoneNumberType.

But if you’re keeping it simple, enums are a great way to start.

P.S. In case you missed it, Practicing Rails is going to be included in the Ruby Book Bundle, launching on Monday, July 6. Get it and 5 other great Ruby books at a huge discount!

Pushing through tutorials, and still not learning anything?

Have you slogged through the same guide three times and still don't know how to build a real app?

In this free 7-day Rails course, you'll learn specific steps to start your own Rails apps — without giving up, and without being overwhelmed.

You'll also discover the fastest way to learn new Rails features with your 32-page sample of Practicing Rails: Learn Rails Without Being Overwhelmed.

Sign up below to get started:

Powered by ConvertKit

Did you like this article? You should read these:

Comments