ORM & Data Access · Ruby

ActiveRecord

Rails' ORM is the textbook implementation of the Active Record pattern Martin Fowler named — the row, the class, and the data-access methods are one object. Naming conventions do most of the configuration; you write models, not mappings. It set the bar that nearly every modern web ORM still measures itself against.

Ruby on RailsConvention over ConfigurationActive Record Pattern
← Back to Database Side
Quick Facts

At a Glance

Core Ideas

  • Class-to-table mapping by name: class Order < ApplicationRecord auto-binds to the orders table.
  • Attributes from columns: AR introspects the schema at boot — no field declarations required.
  • Associations: has_many :line_items, belongs_to :customer. Reads like English, generates SQL underneath.
  • Scopes: reusable named queries — scope :active, -> { where(status: "active") }.
  • Migrations: Ruby DSL for schema changes — add_column, create_table, reversible by default.
The Pieces

What You'll Touch

ActiveRecord

The ORM layer of Rails. Usable standalone, but ~99% of usage is inside a Rails app.

Arel

The relational algebra layer underneath AR's query DSL. You drop here when chained methods can't express what you need.

Migrations & schema.rb

Schema is canonical in db/schema.rb (or structure.sql) — generated from migrations, checked in.

Validations & Callbacks

Model-level validation, lifecycle hooks (before_save, after_commit). Powerful and dangerous in equal measure.

Sequel (alternative)

The Ruby Data Mapper. Cleaner separation, more SQL-shaped. Loved by people who outgrew AR's magic.

Bullet

The N+1 detector. Practically required on any Rails app of size — surfaces missing includes in dev and CI.

Why It Wins

What ActiveRecord Buys You

Convention Over Configuration

Tables plural, classes singular, foreign keys named {model}_id, timestamps in created_at/updated_at. Follow the conventions and there's almost no mapping code. Break them and you can override with a single line. The economy of the framework comes from this discipline.

The Query DSL Is Genuinely Pleasant

Order.active.includes(:line_items).where("total > ?", 100).order(created_at: :desc).limit(20). Lazy until enumerated, chainable, easy to read. Twenty years on, nothing in the JS or Python world has fully matched the ergonomics.

Migrations You Can Roll Back

Most migrations are reversible automatically — add_column implies remove_column on rollback. The exceptions (data backfills, drops) require an explicit up/down. The DSL nudges you toward safe operations.

Tooling Maturity

Twenty years of Rails apps have produced a deep bench of gems: bullet, scenic (database views), strong_migrations, annotate, rails-erd. The ecosystem solves problems before you find them.

The Sharp Edges

Where People Get Cut

Callbacks Are a Tar Pit

after_save :send_welcome_email is fine — until the test fixture creation triggers SendGrid, until the rake task creates a million records and queues a million emails, until the callback chain forms a cycle. Mature Rails teams move side effects out of callbacks into explicit service objects.

N+1 Is Still N+1

Forget includes and the loop fires a query per row. Bullet catches it; reviewers should too. The same problem every ORM has — Rails just makes it especially easy to commit.

Fat Models

"Skinny controllers, fat models" was the original advice. Then models became 2000-line god classes. The current consensus is concerns, service objects, and form objects — break responsibilities out before AR turns into a junk drawer.

Schema Drift

Two devs branch, both write migrations against the same parent, both merge — schema.rb conflicts and timestamp ordering matters. Convention is to renumber the loser; tooling exists to automate it. Painful enough at scale that some teams switch to structure.sql.

The Wider Critique

The Active Record pattern couples persistence to the domain object. For complex domains (DDD, CQRS, event sourcing), that coupling is the problem, not the solution. Rails apps that grow past a certain complexity often introduce a Repository / Data Mapper layer on top — at which point Sequel starts looking attractive.

Patterns

How Teams Actually Use It

  • Scopes for reusable queries — keeps controllers and views from string-concatenating where clauses.
  • Service objects for multi-model orchestration — OrderCheckout.new(...).call instead of before_save chains.
  • strong_migrations gem to reject unsafe migrations in CI (add_column with default on a big table, add_index non-concurrently).
  • Counter caches for Comment.where(post_id: 1).count hot paths — denormalize the count onto the post.
  • Drop to Arel or raw SQL for window functions and recursive CTEs. find_by_sql is fine when AR's DSL is in the way.
Continue

Related