DEV Community

Cover image for Implementing Multi-Select Enums in Ruby on Rails with PostgreSQL
Sulman Baig
Sulman Baig

Posted on • Originally published at sulmanweb.com

Implementing Multi-Select Enums in Ruby on Rails with PostgreSQL

In our previous article (linked below), we explored how to implement single-select enums in a Ruby on Rails application. In this post, we will extend that discussion to include the implementation of multi-select enums using arrays in Ruby on Rails with PostgreSQL.

Introduction to Multi-Select Enums

Enums are a convenient way to handle a set of predefined values in your application. Sometimes, however, you might need to associate multiple values with a single record. For example, an article might belong to several categories such as "blog," "snippet," or "vlog." In this case, using an array of enums is a suitable solution that provides the flexibility to assign multiple categories.

Why Use Enum Arrays?

PostgreSQL enum arrays offer several advantages:

  • Better performance compared to string arrays due to internal optimizations.

  • Type safety at the database level, reducing the chance of invalid data being saved.

  • Reduced storage space compared to text arrays, making your database more efficient.

  • Improved query performance with GIN (Generalized Inverted Index) indexing, which is particularly useful for fast lookups on array columns.

Setting Up the Migration

Let's implement a multi-category system for an Article model. First, generate the migration:

rails g migration AddCategoriesToArticles categories:enum
Enter fullscreen mode Exit fullscreen mode

Modify the generated migration to define an enum type and add the new column:

class AddCategoriesToArticles < ActiveRecord::Migration[7.2]
  def change
    create_enum :categories_enum, %w[
      blog
      snippet
      vlog
    ]
    add_column :articles, :categories, :enum, enum_type: :categories_enum, array: true, default: []
    add_index :articles, :categories, using: 'gin'
  end
end
Enter fullscreen mode Exit fullscreen mode

In the migration above, the array: true option enables multiple values to be stored in the categories column. For optimal performance with array columns, PostgreSQL recommends using a GIN index to speed up querying.

Apply the migration:

rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Model Configuration

Next, define the enum in your Article model with proper validations and methods:

class Article < ApplicationRecord
  validates :categories, array_inclusion: { in: %w[blog snippet vlog] }

  # Remove duplicates before saving
  def categories=(types)
    super(types.uniq)
  end

  # Convenience method for checking category presence
  def has_category?(category)
    categories.include?(category)
  end
end
Enter fullscreen mode Exit fullscreen mode

In this model, the array_inclusion validation ensures that only the specified strings are included in the categories array. The custom setter method categories= removes duplicate values before saving them to the database, ensuring data integrity.

Usage Examples

Here are some examples of how you can work with multi-select enums in Rails:

# Create an article with multiple categories
article = Article.create(categories: ['blog', 'vlog'])

# Add a category
article.categories << 'snippet'
article.save

# Query articles with a specific category
articles_with_blog = Article.where("'blog' = ANY(categories)")
Enter fullscreen mode Exit fullscreen mode

The array_inclusion validation ensures that only permitted values are stored, while the custom setter prevents duplicate categories. For optimal query performance when filtering by categories, PostgreSQL will utilize the GIN index automatically.

Conclusion

PostgreSQL enum arrays in Rails provide a robust solution for handling multi-select fields with better performance and data integrity compared to string arrays. This pattern is particularly valuable for content management systems, tagging systems, or any scenario that requires multiple categorizations. By combining the use of enum arrays and proper indexing, you can ensure that your application remains performant as the data grows.


Happy Coding!

Top comments (0)