Refactoring Pseudo-Enum Models to Enums
As a developer, we’ve all been there - stuck with outdated, unmaintainable codebases that seem to defy the laws of good design. In this post, we’ll explore a common pitfall in Ruby on Rails: pseudo-enums, and how to refactor them into real enums for better maintainability and scalability.
What are Pseudo-Enums?
In Rails, a pseudo_enum is a column in your database that stores an integer value representing one of several predefined statuses. However, unlike true enums, pseudo_enums don’t provide any additional functionality or validation beyond simple integer checks. This can lead to maintenance issues down the line as your application grows and evolves.
The Problem with Pseudo-Enums
In the original code snippet provided by the question author, they’re attempting to update a status
column in their articles
table using the following SQL:
execute <<~SQL
# Write SQL here
SQL
However, this approach has some significant issues. Firstly, it requires a manual loop through each article to update its status, which can become brittle and prone to errors over time. Secondly, by updating the status
column directly, we’re losing the benefits of having an enum in the first place.
Refactoring to Enums
To refactor our pseudo_enum into a real enum, we need to create a new migration that adds the necessary columns and indexes to our database table.
class AddStatusToArticles < ActiveRecord::Migration[6.1]
def change
add_column :articles, :status_id, :integer
add_index :articles, :status_id
# Create an enum for statuses
create_enum 'ArticleStatus'
execute <<~SQL
UPDATE articles SET status_id = (SELECT id FROM article_statuses WHERE name = article.status_name);
SQL
end
end
In this refactored code, we’re using Rails’ built-in enum
feature to define a new enum called ArticleStatus
. This enum will provide us with the benefits of having true enums in our database.
Creating the Enum
To create the enum, we simply call the create_enum
method and pass the name of the enum as an argument. In this case, we’re creating an enum called ArticleStatus
.
class CreateArticleStatuses < ActiveRecord::Migration[6.1]
def change
execute <<~SQL
CREATE TYPE article_statuses AS ENUM ('draft', 'in_review', 'reviewed', 'published', 'deleted');
SQL
end
end
This creates a new enum type called article_statuses
with five predefined values.
Updating the Model
With our pseudo_enum refactored into a real enum, we can now update our model to use the new column instead of the old one.
class Article < ApplicationRecord
enum status: { draft: 0, in_review: 1, reviewed: 2, published: 3, deleted: 4 }, _default: :draft
# Update the status method to return the human-readable name
def status_name
I18n.t('article.statuses.' + [status, nil].max)
end
# Add a scope to retrieve articles by status
scope :published, -> { where(status: :published) }
# Add a scope to retrieve articles that are not published
scope :unpublished, -> { where(status: [:draft, :in_review, :reviewed, :deleted]) }
end
In this updated model, we’ve added an enum
column called status_id
, which stores the ID of the corresponding enum value. We’ve also added a new method called status_name
, which returns the human-readable name for each status.
Updating the Database
To update our database to use the new column and values, we can execute the following SQL:
execute <<~SQL
UPDATE articles SET status_id = (SELECT id FROM article_statuses WHERE name = article.status_name);
SQL
This updates the status_id
column in the articles
table to match the corresponding enum value.
Conclusion
Refactoring pseudo-enums into real enums can significantly improve the maintainability and scalability of your application. By creating a new enum type, adding the necessary columns and indexes, and updating your model to use the new column, you can ensure that your codebase remains consistent and easy to work with over time.
In this post, we’ve explored how to refactor pseudo-enums in Ruby on Rails using enums. We’ve covered the basics of creating an enum, updating your database, and updating your model to use the new column. With these steps, you can take your first step towards creating a more maintainable and scalable application.
Last modified on 2024-04-29