Refactoring Pseudo-Enums to Enums in Ruby on Rails for Better Maintainability and Scalability

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