After you pick up the Rails basics, you still have a lot to learn. You have to understand gems, DSLs, refactoring, testing, and the deeper parts of Rails itself.

With the Ruby Book Bundle, launching on Monday, July 6, you can kickstart your Rails education with Practicing Rails and 5 other great Ruby books at a huge discount. Sign up to get a reminder when the bundle sale starts!


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:

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

List the values that attribute can take:

app/models/phone.rb
1
2
3
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:

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

You’ll see this:

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

You can change that attribute using either strings or ints:

1
2
3
4
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:

1
2
3
4
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:

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

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

1
2
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:

1
2
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
1
2
3
4
<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
1
2
3
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
1
2
3
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!

Did you like this post? You should read these:

Finished another Rails tutorial and still don’t know how to start?

Have you slogged through the same guide three times and still can't retain enough to write apps on your own?

In my free 7-part course, you’ll discover the fastest way to learn and remember new Rails ideas, so you can use them when you need them. And you'll learn to use what you already know to build your own Rails project.



Comments