Unveiling the Power of Enums for Scoping Queries in Rails

Placeholder Avatar
Richard Gonzales June 25, 2024

Provided in ActiveRecord, enums are used to define a set of named values for an attribute on a model. The basic implementation for enums are for attributes that relates to status, stages and states.


class Task < ApplicationRecord
  enum status: { pending: 0, active: 1, completed: 2, paused: 3, archived: 4 }
end

Enums provide symbolic names for values, making the code more readable and self-explanatory. For the code above, 0, 1, 2, 3 and 4 will be saved in the field status for the Task model and the respective table field in the database. Rather than the numbers, the symbolic names pending, active, completed, paused, and archived will be referred to in the code.

Enums make sure that only the predefined values can be used for the specified attribute, thus reducing errors. The values are stored as integers in the database which is more efficient in storage than strings. Enums will also automatically generate helpful methods and scopes for querying.

Let's walk though several powerful features of enums:

  1. Query methods.

    The generated query methods for each value makes querying based on the enum's state very straightforward. With the code above, we can write the query methods like:

    
    pending_tasks = Task.pending
    active_tasks = Task.active
    completed_tasks = Task.completed
    paused_tasks = Task.paused
    

     
  2. Scopes for enums.

    We can build custom scopes for each enum values which in turn provides complex but readable queries.

    
    class Task < ApplicationRecord
      enum status: { pending: 0, active: 1, completed: 2, paused: 3, archived: 4 }
    
      scope :recent_pending, -> { pending.where('created at > ?', Date.current) }
    end
    
    recent_pending_tasks = Task.recent_pending
    

     
  3. Conditional queries.

    It is easier to construct conditional queries based on the value of the enum attribute. This is useful in controller actions or services that requires multiple criteria when filtering records.

    
    tasks = Task.where(status: [:paused, :archived])
    

     
  4. State management

    Enums are often used to manage state transitions. This is very useful for workflows or state machine scenarios where filters are required based on the record's state.

    
    class Task < ApplicationRecord
      enum status: { pending: 0, active: 1, completed: 2, paused: 3, archived: 4 }
    
      scope :not_archived, -> { where.not(status: archived) }
    end
    
    workable_tasks = Task.not_archived
    

     
  5. Integration with form helpers

    Enums allows easy creation of select drop downs in forms This makes the values in forms are consistent with the defined enum values.

    
    
    <%= form_with model: @task do |f| %>
      <%= f.label :status %>
      <%= f.select :status, Task.statuses.keys.map { |s| [s.humanize, s] } %>
      <%= f.submit %>
    <% end %>
    
    

     
  6. ActiveRecord callbacks

    Enums can be combined with ActiveRecord callbacks to perform actions based on the state of the enum attribute.

    
    class Task < ApplicationRecord
      enum status: { pending: 0, active: 1, completed: 2, paused: 3, archived: 4 }
    
      after_update :notify_managers, if: status_changed?
    
      def notify_managers
        # send notifications to managers about task changes
      end
    end
    

     

With these powerful implementations of enums, there are some limitations: First is the database support, enum support is built into ActiveRecord and doesn't rely on the database's enum types. Enum will work with any database supported by ActiveRecord. Another is the migration considerations, if there will be changes in the enum values, careful handling is necessary to avoid migration inconsistencies.

Enums in Ruby on Rails provides powerful and expressive means to handle attributes with predefined set of values either in scope queries, state management and thus improving code readability and maintainability. Leveraging this feature also ensures consistency across the application.