Press enter or click to view image in full size
Enum in Rails are very useful to create list of parameters for active records. This range from statuses to many other use cases. To make it easier we will use the status case as it’s a very common one and focus on Rails 7. Previous versions of Rails have different form_helpers for the select element.
In ActiveRecord
Let’s say we have a Post model and we need to track its status. Well the first thing to do is make sure that you have a status field in your database.
rails g migration AddStatusToPost status:integerTo speed things up, edit the resulting migration file and add an index on the status column
class AddStatusToPost < ActiveRecord::Migration[7.0]
def change
add_column :posts, :status, :integer
add_index :posts, :status
end
endYou can decide at database level which status you want to assign by default. But you can also decide of this at Model level and this is what we are going to be using for this example.
Update the database rails db:migrate and tada! Step 1 completed.
Open you Post model and let’s work on the status.
class Post < ApplicationRecord
belongs_to :user validates :title, presence: true enum status: { draft: 0, published: 10, in_review:20 }
end
The advantage of this approach is that you control the status value in the database. This avoid mistakenly add a status at the wrong location and shifting the entries.
enum status: [:draft, :published, :in_review] is forcing the following key value binding: draft is 0, published is 1, etc. so adding something like reported can trigger unexpected behavior if not done properly such as: enum status: [:draft, :published, :reported, :in_review] as in_review is now 3… and previous posts flagged with this status are now displayed as reported. Oops… we did it again and messed up the database.
This is why fixing the recorded values is a much better idea.
Now, something debatable, setting the default value from the Model. This feature was introduced in Rails 6 and let you decide which status is going to be used as default.
enum status: { draft: 0, published: 10, in_review: 20 }, _default: :draft will set draft as the new default status value. So if you are not passing a value when creating a record you are still safe a have a value for the its status.
Additional benefits of using enum in your ActiveRecords are that it comes with a lot of predefined scopes and more.
p = Post.firstp.draft! # will change the first Post status to `draft`
p.draft? # true
p.published? # falsePost.in_review # returns a collection of all posts that are in_reviewPost.not_draft # will return all the posts that have a status different from draft.
Using enum will also enforce inclusion validation. Meaning that when you save a record the status value will be check against the possible values stored in the Post model.
One last thing before moving to the ActionView. Yes you can also write enum status: { draft: 'draft', ...} but saving as an integer in the database will boost performances. Strings are usually slower to manipulate than integers.
Connecting to the Form!
Now that we have our “backend” set up, time to link it to an ActionView Form.
Out of the box:
<div>
<%= form.label :status %>
<%= form.select :status, Post.statuses.keys %>
</div>This works, but might not display the values nicely, and we might also need some default value depending on edit or create, etc.
So, here is a more sophisticated option:
<div>
<%= form.label :status %>
<%= form.select :status, Post.statuses.keys.map{ |key| [key.humanize, key] }, selected: @post.status || :draft %>
</div>In this case we 1) humanize the key (for more information about how to use humanize, refer to https://apidock.com/rails/v6.1.3.1/String/humanize) and 2) we default to either draft when creating a new record or the current status value for an existing record (this assumes that the same form partial is used for both actions: create and edit)
Depending on your use case you can also use include_blank: or prompt: as part of the select as mention in the official documentation (https://api.rubyonrails.org/v7.0.2/classes/ActionView/Helpers/FormOptionsHelper.html)
One last work: Keep in mind that the key is what “stores” the value as the record id is in the value section of the object we created in the Post model. This is why we manipulate the keys and not the values.
To convince yourself about this:
rails c
irb(main):001:0> Post.statuses
=> {"draft"=>0, "published"=>10, "in_review"=>20}
irb(main):002:0>And voilà!