Quick Tip: Use Enums in Rails for Mapped Values

William Kennedy
William Kennedy
Share

When I worked in a call center, we used to mark cases with different statuses. This allowed upper management to get a handle on where cases stood, what the bottlenecks were and flow of calls. Thankfully it has been a long time since I worked in a call center, but I have pondered how I would accomplish this task with Rails. Thankfully, Rails has a solution known as Enums.

When you are adding statuses to a model, you might be tempted to use strings to set the status. This makes perfect sense because different statuses tend to be named things like ‘pending’, ‘under review’, and ‘completed’.

However, Rails encourages you to take advantage of Enums to replace these strings with integers. It seems counter-intuitive at first, but it is actually a perfect fit for the problem.

Enums allow you to map string values to integers in the database so they can be queried by name:

class CustomerCase < ApplicationRecord
  enum status: [:open, :closed,:under_review, :pending]
end

Now in the Rails console, we can do the following:

$ customer_case.open?

We can even use scopes:

$ CustomerCase.open

Just so we can really practice using Enums, I highly recommend creating a small Rails App:

$ rails new call_center

Change into that directory and generate a quick scaffold with lots of attributes:

$ rails generate scaffold CustomerCase title:string description:text status:integer agent:string

Notice that the status field is an integer.

Migrate the database:

$ rails db:migrate

Open up app/models/customer_case.rb and input the following:

class CustomerCase < ApplicationRecord
  enum status: [:open, :closed, :under_review, :pending]
end

Now we have a small application up and running. Go ahead and create a couple of cases yourself with different attributes in the rails console (rails c):

$ CustomerCase.create(title: "Case 1", description: "Our first case", status: :open, agent: "Me")
=> #<CustomerCase id: 1, title: "Case 1"...status: "open"
$ CustomerCase.create(title: "Case 2", description: "Our second case", status: :pending, agent: "Me")
=> #<CustomerCase id: 2, title: "Case 2"...status: "pending"

It’s also a good idea to change the line in app/views/customercase/form.html.erb that corresponds to the Enums so you can pick the actual statuses:

...
</div>
<div class="field">
  <%= f.label :status %><br>
  <%=  f.collection_select :status, CustomerCase.statuses.map{ |a| [a.first,a.first] },  :first, :second %>
</div>

I suggest creating as many different cases as possible and testing the different possibilities from the Rails console:

$ bundle exec rails c
$ cse = CustomerCase.create(title:"Case Title", description:"what has happend", status: "open" )
$ cse.open?
=> true
$ cse.closed?
=> false
$ cse.pending?
=> false
$ CustomerCase.open.to_a
CustomerCase Load (0.1ms)  SELECT "customer_cases".* FROM "customer_cases" WHERE "customer_cases"."status" = ?  [["status", 0]]
=> #<ActiveRecord::Relation..

The last example calling the open scope shows the SQL used to query the database. The query is using 0 for the value, not open, as expected.

You can also change the status of the case:

$ cse.pending!

(0.1ms) begin transaction SQL (1.0ms) UPDATE “customercases” SET “status” = ?, “updatedat” = ? WHERE “customercases”.”id” = ? [[“status”, 3], [“updatedat”, 2016-10-05 13:34:28 UTC], [“id”, 3]] (0.6ms) commit transaction => true

Enums, when used properly, can be a great addition to creating readable code. Now that we have explored Enums along with a possible use case, I hope that it will make implementing other Enum-related features into your Rails app easier.

Frequently Asked Questions about Using Enums in Rails

What are the benefits of using Enums in Rails?

Enums in Rails are incredibly beneficial for several reasons. Firstly, they allow you to map values to integers in the database, which can significantly improve performance. This is because integer comparison is faster than string comparison. Secondly, Enums provide a set of helper methods that can be used to quickly query the status of an object. This can simplify your code and make it more readable. Lastly, Enums can enforce data integrity by ensuring that only valid states can be assigned to the object.

How do I define an Enum in Rails?

Defining an Enum in Rails is straightforward. You can do this by adding a column to your database table, typically of integer type. Then, in your model, you can declare the Enum using the enum keyword followed by a hash. The hash keys represent the names of the Enum values, and the hash values represent the corresponding integer values in the database. Here’s an example:

class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
end

How do I query objects based on their Enum values?

Once you’ve defined an Enum, Rails automatically provides a set of query methods that you can use. These methods are named after the Enum values. For example, if you have a status Enum with values draft, published, and archived, you can retrieve all published posts like this:

Post.published

Can I use strings instead of integers for Enum values in the database?

While it’s technically possible to use strings for Enum values in the database, it’s not recommended. This is because integer comparison is faster than string comparison, which can lead to performance improvements. Additionally, using integers can save storage space in the database.

How do I update the Enum value of an object?

You can update the Enum value of an object just like you would update any other attribute. However, you should use the Enum value names, not the corresponding integer values. Here’s an example:

post = Post.find(1)
post.status = "published"
post.save

Can I add more values to an Enum after it’s been defined?

Yes, you can add more values to an Enum after it’s been defined. However, you should be careful when doing this to avoid conflicts with existing data. It’s recommended to add new Enum values at the end of the list to ensure they get assigned new, unused integer values.

What happens if I try to assign an invalid value to an Enum?

If you try to assign an invalid value to an Enum, Rails will raise an ArgumentError. This helps enforce data integrity by ensuring that only valid states can be assigned to the object.

Can I use Enums with other database column types, like boolean or date?

Enums in Rails are typically used with integer columns. While it’s technically possible to use Enums with other column types, it’s not recommended and may lead to unexpected behavior.

How do I use Enums with form inputs in Rails?

You can use Enums with form inputs by using the Enum value names as the input values. Rails will automatically convert these to the corresponding integer values when saving the object. Here’s an example:

<%= form_for @post do |f| %>
<%= f.select :status, Post.statuses.keys %>
<% end %>

Can I use Enums in Rails with non-English characters?

Yes, you can use Enums in Rails with non-English characters. However, you should be aware that this may affect the automatically generated query methods. It’s generally recommended to use English characters for Enum values for maximum compatibility.