Enum in Rails explained

Enum is a shortcut for the enumerated term, which means a set of named values in computer programming. When it comes to Rails, we often mean statuses when we are talking about the set of named values. For example, we can have the user in the application that can be either active or deleted. In addition, we can have an article that can be draft, published, or archived.

Rails make it easy to use enums and save us a lot of time by providing magic methods to make our lives easier. This article is a perfect starting point for someone unfamiliar with Rails enums yet and a perfect checklist for developers who work with such structures daily.

Creating database column for enum

I mentioned in the beginning that enum is a set of named values. In the database, we would store status as an integer as it is a very efficient solution when it comes to performance.

Let’s create then the User model that will consist of the following columns: name, email, and status. The status column will be our enum:

rails g model User name:string email:string status:integer
bundle exec rake db:migrate

That’s it. You don’t have to do anything more in terms of the enum’s definition on the database level. We can now update the User model to let Rails know that we want to turn the status column into enum.

Enum definition in the model

In the Rails model, you have the enum method at your disposal. It accepts a hash where the key is the column name, and the value is the second hash with statuses mapped to the integers:

class User < ApplicationRecord
  enum status: {
    invited: 0,
    active: 1,
    deleted: 2
  }
end

Alternatively, you can use shorter form:

class User < ApplicationRecord
  enum status: [:invited, :active, :deleted]
end

I don’t recommend the second form because if you change the order of the values, you will break the whole logic inside the application. The first solution is better as you always know which status is mapped to which integer.

The User model and the status column are now ready to benefit from the dynamically created methods by Rails; let’s play with them a little bit.

To do the test, we need some test records in the database. Open the console, and type the following code:

user = User.create!(name: 'John Doe', email: '[email protected]', status: :active)

As you can see on the above code, although the status column stores integer, I was able to pass the symbol as the value, and it was correctly saved in the database. It’s because Rails knows that status is enum, and it replaces the values automatically.

Verifying the status

We would like to check if the user is active:

user.active? # => true

For each defined status, Rails automatically creates a method with the same name and the question mark that returns a boolean value depending on the current status. If we did not define enum, instead of the above code, we would have to do the following equality check:

user.status == 1

It’s not readable at all. The developer who is not aware of the status mapping won’t guess what status we are asking for.

Working with the scopes

If we are working with the statuses, we would probably want to pull records only with the given status sooner or later. Also, in such a case, Rails has got our back. So, for example, if we would like to pull only invited users, we can use the following scope:

User.invited

For every status defined in enum, we have the class method with the same name available, a standard scope that you can use when querying the database. Simple as that.

That’s not the end in terms of the scopes that are automatically created by Rails. Since Rails 6, we can benefit from negative scopes. This means that if we want to query all users except those invited, we already have a scope. All we need to do is to add not_ suffix to the status name:

User.not_invited

The above form is straightforward and very meaningful. Without the enum, we would need to use something like that:

User.where.not(status: 0)

Again, we would not remember what status 0 stands for after a few weeks from creating such code.

Updating the data

For sure, we might want to compare the status and query the database in terms of the status, but how about updating the user’s status? Well, it’s also covered by the Rails’ magic.

If you want to update the user’s status to deleted, you can either use the explicit update call:

user.update(status: :deleted)

or use the bang method with the status name:

user.deleted!

The first option would be better if you update more attributes at once, while the second is the best fit if you just want to change the status.

Refactoring enums

Even though our status enum is very useful for managing the user data, we can improve it a little bit regarding the naming and default value. Rails allows us to pass some extra options to the enum.

Default value

If we did not specify the status during the user record creation, we would have a nil value. We don’t want users without statuses, so let’s fix it on the enum level:

class User < ApplicationRecord
  enum status: [:invited, :active, :deleted], _default: :invited
end

After I passed the _default option, every newly created user would have the invited status unless the status was passed explicitly. I would also recommend updating the default value for the status column on the database model because it helps to keep the data integrity and the default option is only available from the Rails 6.1.

Adding prefixes and suffixes

Sometimes the enum value is not meaningful enough, and it’s better to apply the enum name as a suffix or prefix. In our case, we can select which version fits the best:

user.active?
user.active_status?
user.status_active?

I think the second version is the best and would also work with invited and deleted status. We can alter the default naming by passing _suffix or _prefix option:

class User < ApplicationRecord
  enum status: [:invited, :active, :deleted], _default: :invited, _suffix: true
end

We can now use the updated version for equality check, updating, and querying the data:

user.active_status?
user.invited_status!
User.deleted_status

In my opinion, this version works the best in all cases, but it’s up to you which one you choose.

The next steps

Enums were introduced in Rails 4.1, so there is a high possibility that you can use them in your project if you are not using them already. I hope you enjoyed this short introduction to enums, and you are looking for more ways to play with them.