How to Use Enums in Rails
source link: https://blog.saeloun.com/2022/01/05/how-to-use-enums-in-rails
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK