Simplifying Using Enum In Rails
Ruby was built with a vision of providing a high level, genuine object-oriented programming language that offers ease of use and understanding. With time, the language has evolved and together with the framework Rails, it has persistently been shaping productive solutions with simplicity.The use of ENUM in Ruby On Rails is one of the ways through which we can explore this aspect.
If you are unfamiliar with ENUMs, enum is a data type used to assign names to integer values that behave as a constant. With the introduction of names to the 0s and 1s of the data, it provides readability and aids in its maintenance through the self-explanatory codebase.
Let’s explore how we can leverage enums.
Setting Up The Application For Using enum
The first step would be to create a table with an enum column or add a column to the existing table in the database. This is pretty straightforward as adding an integer field would do the trick.
Let us set up a table for tracking candidate progression for a HR company.
rails g migration CreateCandidates name:string status:integer
This migration creates a table Candidate
with a string field name and an enum field status
.
ruby
class CreateCandidates < ActiveRecord::Migration[6.1]
def change
create_table :candidates do |t|
t.string :name
t.integer :status
t.timestamps
end
end
end
For an existing table Candidate
, if we want to add a new enum field called status, we add the following migration.
rails g migration AddStatusToCandidates status:integer
The generated migration file looks like below:
ruby
class AddStatusToCandidates < ActiveRecord::Migration[6.1]
def change
add_column :candidates, :status, :integer
end
end
Then, we simply run the migration to reflect these changes:
bundle exec rake db:migrate
Now that we have set up an enum field, let’s explore how we can use it.
Declaring enum
A candidate goes through a progression of being at the state of applied
, shortlisted
, interviewed
, hired
or rejected
.
We can represent this progression in the Candidate
model as an array or a hash where we start with enum, then the column name and then the collection of values. By default the declared enums can be used as a scope. If we do not want them to be used as scope, we can disable it by explicitly stating scopes: false
.
Enum values as an array
When representing the collection of values as an array, we represent it as a collection of symbols in either of the ways shown below.
ruby
# frozen_string_literal: true
class Candidate < ApplicationRecord
enum status: [:applied, :shortlisted, :interviewed, :hired, :rejected],
scoped: false
end
OR
ruby
# frozen_string_literal: true
class Candidate < ApplicationRecord
enum status: %i(applied shortlisted interviewed hired rejected)
end
The integer values represented by the collection of status are 0
, 1
, 2
, 3
and 4
in their respective order.
Enum values as a hash
When representing the collection of values as a hash, we have the flexibility of assigning any integer value to the collection.
ruby
# frozen_string_literal: true
class Candidate < ApplicationRecord
enum status: { applied: 0, shortlisted: 1, interviewed: 2, hired: 3, rejected: 4 }
end
OR
ruby
# frozen_string_literal: true
class Candidate < ApplicationRecord
enum status: { applied: 1, shortlisted: 3, interviewed: 5, hired: 7, rejected: 9 }
end
Using either an array or a hash for declaring enums will yield us the same output when we fetch for the values.
ruby
(main)> Candidate.statuses
=> {"applied"=>0, "shortlisted"=>1, "interviewed"=>2, "hired"=>3, "rejected"=>4}
But it is recommended to use hash to declare the enums since the values of an array enum is strictly dependent on their order. So a mixup of the order can corrupt the data.
Using enum
We can use the declared enum values in all the CRUD operations.
Create
ruby
(main)> Candidate.create(name: 'First Test Candidate', status: "applied")
=> #<Candidate:0x000000011612eaf0 id: 1, name: "First Test Candidate",
created_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
updated_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
status: "applied">
Read
ruby
(main)> Candidate.where(status: :applied)
=> [#<Candidate:0x000000010331e968 id: 1, name: "First Test Candidate",
created_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
updated_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
status: "applied">]
The enums can also be used as scope for query:
ruby
(main)> Candidate.applied
=> [#<Candidate:0x0000000115847108 id: 1, name: "First Test Candidate",
created_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
updated_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
status: "applied">]
ruby
(main)> candidate = Candidate.find 1
=> #<Candidate:0x0000000110f72c98 id: 1, name: "First Test Candidate",
created_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
updated_at: Sun, 12 May 2024 12:55:10.172380000 AEST +10:00,
status: "applied">
(main)> candidate.applied?
=> true
Update
Updating the status via assignment:
ruby
(main)> candidate.status = :shortlisted
=> :shortlisted
(main)> candidate.save!
=> true
(main)> candidate.shortlisted?
=> true
Or using the ! method:
ruby
(main)> candidate.interviewed!
=> true
(main)> candidate.interviewed?
=> true
Delete
ruby
(main)> Candidate.interviewed.delete_all
=> 1
(main)> Candidate.count
(34.9ms) SELECT COUNT(*) FROM "candidates"
=> 0
Same is for
ruby
Candidate.create(name: "First Test Candidate", status: :applied)
Conclusion
Use of enum can be a great way to simplify and represent numerical progressions of situations for better legibility.