How to Use Enums in Rails


What is enum?

Enum (short form of Enumeration) is a data type used to assign names to integral constants. The names are identifiers that behave as constants in language and makes a program easy to read and maintain.

ActiveRecord::Enum was introduced in Rails 4.1. An enum is an attribute where the values map to integers in the database and can be queried by name. Enums also give us the ability to change the state of the data very quickly. This makes it easy to use enums in Rails and saves a lot of time by providing dynamic methods.

Creating database column for enum

In Rails, adding an enum to a model is pretty simple as adding an integer column to the table.

Let’s assume we have a Rails application that has a Post model. A post can be drafted, published, archived, or trashed. Instead of using these strings in the Post table to denote its status, we can use integers like 0, 1, 2.

Assuming the Post table already exists in our application the DB migration to add status as enum will look as below:

class AddStatusToPosts < ActiveRecord::Migration[7.0]
  def change
    add_column :posts, :status, :integer, default: 0
  end
end

Note: We have added the default value as 0 in the migration. This means the post status will be draft by default.

Various ways to define enum in model

We run the migration using rake db:migrate and in our Post model, we need to define the enum as shown in the below example. The enum method expects the column attribute name which it should refer to and the second parameter is a list of values a post status can have.

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, [ :draft, :published, :archived, :trashed ]
end

We can also use the %i() format to declare enums.

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, %i(draft published archived trashed)
end

Since we have used arrays for declaring enums, draft will be mapped to 0 in the database, published will be mapped to 1, and so on.

Instead of an array, we can also pass a hash where the key will be draft, published, etc., and the values can be assigned as per the developer’s choice.

# app/models/post.rb

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

The recommended way is to use a hash instead of an array because if we change the order of the values, we will break the whole logic inside our Rails app.

How enum works

We can fetch all the enum values or a particular enum value using the pluralized form of the column name.

Post.statuses
=> { "draft" => 0, "published" => 1, "archived" => 2, "trashed" => 3 }

Post.statuses[:archived]
=> 2

Post.statuses["trashed"]
=> 3

Post.statuses[:unarchived]
=> nil

Creating a published post

post = Post.create(title: "First post", description: "First post description...")

post.status
=> "draft"

post = Post.create(title: "Second post", description: "Second post description...", status: :published)

post.status
=> "published"

For the First post where status is not passed, it gets set to draft by default. Even if the status column stores integer value, we can pass the symbol :published as a value to the status key. This is because Rails knows that the status column is an enum, and it replaces the values internally from symbol to integer.

Verifying the post status

Rails provide dynamic methods to verify the status of a particular post.

If we create a post and publish it, we usually verify by using post.status == 'published'. With an enum, we can verify using enum helpers provided by Rails.

post.published?
=> true

post.draft? || post.trashed?
=> false

Updating a post status

Similar to verifying the post status using ?, Rails enum provides a helper for updating the enum value. Instead of using post.update(status: :archived), we can use the ! method.

post.archived!

post.published?
=> false

post.archived?
=> true

Working with scopes

Our Rails app has a Post model with different statuses and we would want to pull records only with a given status sooner or later. Rails has added dynamic methods to resolve this query. To fetch all published posts, we might use Post.where(status: "published") query in our Rails controllers. Instead of this, we can use the published method as scope on Post. For every status, we have in our enum, Rails add a class method with the same name. In our case, we have #draft, #published, #archived, and #trashed methods for the Post model.

Post.published
select "posts".* from "posts" where "posts"."status" = $1 [["status", 1]]

With Rails 6, we can also add the negate condition to enum scopes. To fetch all posts that are not published, we can add the not_ prefix to the published method.

Post.not_published
select "posts".* from "posts" where "posts"."status" != $1 [["status", 1]]

We can also disable these enum scopes by adding the scopes: false option to the enum defined in the model. Using the scope methods then will raise NoMethodError.

# app/models/post.rb

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

Post.published
=> NoMethodError: undefined method `published' for #<Class:0x009hg431k236t8>

Prefixes and Suffixes

Sometimes the enum values might not be meaningful and it’s better to apply prefix or suffix to enum name. Let’s take the User model in this case with the status column, defined as enum in our application.

# app/models/user.rb

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

user.active? method can be precisely written as user.active_status? and similar for other enum names. To achieve this, we can add suffix: true as an option to enum.

# app/models/user.rb

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

We can use the updated dynamic methods for equality check, updating and querying on user object or model.

user.invited_status?

user.active_status!

User.deactivated_status

If we pass prefix: true, the above methods will change to:

user.status_invited?

user.status_active!

User.status_deactivated

Prefixes and Suffixes are useful when a model has two columns with enum values. Let’s say our Post model has a category column which is an enum and can take free or premium value.

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }
  enum :category, { free: 0, premium: 1 }
end

If we define these enums without prefix or suffix, the methods Post.free, post.published?, post.premium! will confuse developers which columns these methods are referring to.

Instead, we can add prefix and suffix as per our requirement and call the methods accordingly.

# app/models/post.rb

class Post < ActiveRecord::Base
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }, prefix: true
  enum :category, { free: 0, premium: 1 }, suffix: true
end

Post.free_category

post.status_published?

post.premium_category!

Note:

Rails 7 introduced a new syntax for enum. For more details, please refer to our previous blog post.

Join Our Newsletter