DEV Community

Matthew LaFalce
Matthew LaFalce

Posted on

Leveraging Rails Enums for Cleaner and More Efficient Code

Rails provides a powerful feature called enum that allows you to map attribute values to integers in the database while keeping them human-readable in your application. This feature enables you to use symbols instead of raw integers in your code, making it more expressive and maintainable.

In this blog post, we’ll explore how to define and use enum attributes in Rails, covering everything from basic setup to advanced configurations like scopes, defaults, string persistence, and validations.

Basic Usage

To define an enum attribute, use the enum method in your model. Here’s a simple example:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ] end 
Enter fullscreen mode Exit fullscreen mode

How It Works:

  • The status column is stored as an integer in the database.
  • :pending is mapped to 0, :shipped to 1, :delivered to 2, and :cancelled to 3 based on their order.
  • You can update and query the attribute using either integers or symbols.
order = Order.new order.status = :pending order.pending? # => true order.status # => "pending" order.status = 2 order.delivered? # => true order.status # => "delivered" 
Enter fullscreen mode Exit fullscreen mode

Built-in Scopes

Using enums automatically creates scopes based on the attribute values:

Order.pending # Fetches all pending orders Order.not_pending # Fetches all non-pending orders Order.shipped # Fetches all shipped orders Order.delivered # Fetches all delivered orders Order.cancelled # Fetches all cancelled orders 
Enter fullscreen mode Exit fullscreen mode

If you want to disable these scopes, set :scopes to false:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], scopes: false end 
Enter fullscreen mode Exit fullscreen mode

Setting Default Values

You can set a default value for an enum attribute using the :default option:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], default: :pending end order = Order.new order.status # => "pending" 
Enter fullscreen mode Exit fullscreen mode

Explicit Mapping of Enum Values

If you prefer to define specific mappings instead of relying on implicit integer assignments, use a hash:

class Order < ActiveRecord::Base enum :status, pending: 0, shipped: 1, delivered: 2, cancelled: 3 end 
Enter fullscreen mode Exit fullscreen mode

You can also use string values instead of integers (though this may impact performance):

class Order < ActiveRecord::Base enum :status, pending: "pending", shipped: "shipped", delivered: "delivered", cancelled: "cancelled" end 
Enter fullscreen mode Exit fullscreen mode

Accessing Enum Mappings

To retrieve the integer value mapped to an enum symbol, use:

Order.statuses[:pending] # => 0 Order.statuses["shipped"] # => 1 
Enter fullscreen mode Exit fullscreen mode

This is useful when writing raw SQL queries:

Order.where("status <> ?", Order.statuses[:cancelled]) 
Enter fullscreen mode Exit fullscreen mode

Prefixes and Suffixes

When dealing with multiple enums that share similar values, you can use :prefix or :suffix to prevent conflicts:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], suffix: true enum :payment_status, [ :pending, :completed, :failed ], prefix: :payment end 
Enter fullscreen mode Exit fullscreen mode

This will generate:

order.pending_status! order.shipped_status? # => false order.payment_completed! order.payment_pending? # => false 
Enter fullscreen mode Exit fullscreen mode

Disabling Instance Methods

If you don’t need the automatically generated methods, you can disable them:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], instance_methods: false end 
Enter fullscreen mode Exit fullscreen mode

Validating Enum Values

To ensure only valid enum values are assigned, use :validate:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], validate: true end 
Enter fullscreen mode Exit fullscreen mode

Example validation behavior:

order = Order.new order.status = :unknown order.valid? # => false 
Enter fullscreen mode Exit fullscreen mode

You can also allow nil values:

class Order < ActiveRecord::Base enum :status, [ :pending, :shipped, :delivered, :cancelled ], validate: { allow_nil: true } end 
Enter fullscreen mode Exit fullscreen mode

Handling Errors

If an invalid value is assigned, an ArgumentError will be raised:

order.status = :unknown # Raises 'unknown' is not a valid status (ArgumentError) 
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using enum in Rails models simplifies your code, improves query readability, and enforces data integrity. Whether you’re defining simple status fields or complex multi-enum configurations, the flexibility of enum ensures that your models remain clean and maintainable.

By leveraging scopes, default values, explicit mappings, and validations, you can ensure that your application handles enumerations efficiently while maintaining a high level of clarity in your codebase.

Top comments (0)