Reading the Source · Card 9
What does enum generate?
A one-line declaration that adds predicates, scopes, bang-setters, and a typed casting layer over an integer column. Knowing the surface helps you read the model.
The familiar line
A Post model with three lifecycle states.
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
end The question
List the methods enum adds to a Post instance and to the Post class.
Take a moment. One predicate per value. One setter per value. One scope per value. Plus a couple of class-level helpers and a casting layer that lets you write status as a string or symbol even though the column is an integer.
What Rails generates
On every Post instance:
post.draft? # true if status == "draft"
post.published? # true if status == "published"
post.archived? # true if status == "archived"
post.draft! # sets status = "draft" and saves
post.published! # sets status = "published" and saves
post.archived! # sets status = "archived" and saves
# And the casting layer:
post.status # => "draft" (string, not integer)
post.status = :published # accepts symbols and strings
post.status = "archived" # also fine
# The database column actually stores 0, 1, or 2. On the Post class:
Post.draft # scope: where(status: 0)
Post.published # scope: where(status: 1)
Post.archived # scope: where(status: 2)
Post.statuses # => { "draft" => 0, "published" => 1, "archived" => 2 } If you pass prefix: or suffix:, the generated names get adjusted: enum status: [...], prefix: :state produces post.state_draft?. Useful when two enums share value names across the same model.
Where this happens in Rails
Defined in activerecord/lib/active_record/enum.rb. The method walks the values you provided, calls define_method for each predicate and bang-setter, registers a scope for each value, and installs a custom attribute type that handles the integer<->string casting on read and write. The Post class ends up with a lot of methods, but they all dispatch through the same enum metadata table.
Why this is worth knowing
When you see Post.published.recent, you know the published scope came from the enum, and you can chain it freely. When you see post.archived!, you know it saves immediately (the bang is a clue). When the column type in the migration is integer but the model reads it as a string, you know an enum is doing the casting. Same pattern as belongs_to and has_many: one declaration, a family of methods, a bit of class metadata.