
June 10, 2026
Most Rails developers use belongs_to, has_many, scope, and validates every day.
We type them almost without thinking.
class User < ApplicationRecord belongs_to :company validates :email, presence: true scope :active, -> { where(active: true) }end
But here’s something interesting:
None of those are Ruby keywords.
They’re methods.
In fact, a Rails model is less like a traditional Ruby class and more like a Domain Specific Language (DSL) built on top of Ruby.
Once you see it, it’s hard to unsee.
The Empty Model
A Rails model starts life as a completely ordinary Ruby class.
class User < ApplicationRecordend
At first glance, there’s nothing special here.
User inherits from ApplicationRecord, which inherits from ActiveRecord::Base.
That’s where the magic begins.
By inheriting from Active Record, the class gains hundreds of methods that allow us to describe data, relationships, validations, callbacks, and behavior using a concise DSL.
Associations
Consider this:
class User < ApplicationRecord belongs_to :company has_many :postsend
When Rails reads these lines, it isn’t creating relationships in the database.
It’s executing methods.
Those methods generate additional behavior behind the scenes.
After declaring belongs_to :company, Rails creates methods such as:
user.companyuser.company = companyuser.build_companyuser.create_company
A single line of DSL expands into a collection of Ruby methods.
Validations
The same pattern appears with validations.
class User < ApplicationRecord validates :email, presence: true, uniqueness: trueend
Again, validates is just a method.
Under the hood Rails builds validator objects and registers them with the model.
Later, when save runs, those validators participate in the model lifecycle.
What looks declarative is actually executable Ruby code configuring the class.
Scopes
Scopes are another example.
scope :active, -> { where(active: true) }scope :recent, -> { order(created_at: :desc) }
The scope method dynamically defines additional class methods.
Afterward, we can write:
User.active.recent
This fluent query interface feels almost like a language designed specifically for expressing business rules.
That’s because it is.
Enums
Enums provide one of the clearest examples of Rails generating behavior.
enum :status, { pending: 0, active: 1, suspended: 2}
This single declaration creates methods such as:
user.active?user.pending?user.suspended?user.active!
and scopes like:
User.activeUser.pendingUser.suspended
One line becomes an entire API.
Callbacks
Callbacks often look like configuration:
before_create :generate_tokenafter_commit :send_welcome_email
But they are also method calls.
Rails records these declarations and later injects them into the model lifecycle.
The model is effectively building a pipeline of behavior during class definition.
Delegates
Even delegation participates in the DSL.
delegate :name, to: :company
Rails dynamically creates:
user.name
which forwards the call to:
user.company.name
Again, a tiny declaration generates real Ruby methods.
Beyond the Usual Suspects
Most developers think about associations, validations, and scopes, but Rails models can host many more DSL elements:
attribute :admin, :boolean, default: falseencrypts :ssnhas_one_attached :avatarhas_rich_text :contentaccepts_nested_attributes_for :profilestore_accessor :preferences, :theme, :languageserialize :settings, JSON
Each one teaches the model new capabilities through a simple declaration.
That’s the essence of a DSL.
A Typical Rails Model
After years of development, many models start looking like this:
class User < ApplicationRecord belongs_to :company has_many :posts enum :status, { pending: 0, active: 1, suspended: 2 } validates :email, presence: true scope :active, -> { where(status: :active) } before_create :generate_token delegate :company_name, to: :company, prefix: true has_one_attached :avatar def full_name "#{first_name} #{last_name}" end private def generate_token self.token ||= SecureRandom.uuid endend
Every section represents another piece of the Active Record DSL.
- Associations
- Validations
- Scopes
- Enums
- Callbacks
- Delegates
- Attributes
- Attachments
- Rich Text
- Encryption
- Methods
All living together inside a class that reads almost like a miniature language.
Why This Matters
Understanding that Rails models are DSLs changes how you read code.
Instead of seeing a list of declarations, you start recognizing a series of method calls that configure a class at load time.
That perspective helps explain:
- Why inheritance matters so much in Rails.
- How gems add new model macros.
- Why method lookup becomes important.
- How Rails generates so much functionality from so little code.
- Why Rails code often feels more expressive than plain Ruby.
The next time you write:
belongs_to :company
remember that you’re not using a keyword.
You’re invoking a method that teaches your model new behavior.
And that’s one of the most elegant ideas in all of Rails.
Suggested subtitle:
Most Rails developers use belongs_to, scope, and enum every day. Fewer realize they’re participating in one of the most powerful DSLs in the Ruby ecosystem.
