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
How It Works:
- The
status
column is stored as an integer in the database. -
:pending
is mapped to0
,:shipped
to1
,:delivered
to2
, and:cancelled
to3
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"
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
If you want to disable these scopes, set :scopes
to false
:
class Order < ActiveRecord::Base
enum :status, [ :pending, :shipped, :delivered, :cancelled ], scopes: false
end
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"
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
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
Accessing Enum Mappings
To retrieve the integer value mapped to an enum symbol, use:
Order.statuses[:pending] # => 0
Order.statuses["shipped"] # => 1
This is useful when writing raw SQL queries:
Order.where("status <> ?", Order.statuses[:cancelled])
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
This will generate:
order.pending_status!
order.shipped_status? # => false
order.payment_completed!
order.payment_pending? # => false
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
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
Example validation behavior:
order = Order.new
order.status = :unknown
order.valid? # => false
You can also allow nil
values:
class Order < ActiveRecord::Base
enum :status, [ :pending, :shipped, :delivered, :cancelled ], validate: { allow_nil: true }
end
Handling Errors
If an invalid value is assigned, an ArgumentError
will be raised:
order.status = :unknown # Raises 'unknown' is not a valid status (ArgumentError)
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)