ActiveRecord Basics
ActiveRecord is the object-relational mapping (ORM) layer bundled with Rails. It implements the Active Record pattern, meaning each model class wraps a single database table and each instance of that class wraps a single row. Rather than writing raw SQL, developers call Ruby methods — User.find(1), user.save, user.destroy — and ActiveRecord translates them into the appropriate SQL for whichever database adapter is configured (PostgreSQL, MySQL, SQLite, and others). This abstraction dramatically speeds up development while still allowing raw SQL or Arel when needed for complex queries.
Cricket analogy: ActiveRecord is like a translator between a coach's spoken instructions and the scoreboard operator's button presses -- you call player.save in plain terms and it converts that into the exact scoreboard update, whether the ground uses a Duckworth-Lewis-capable system or a basic manual board.
Migrations and schema management
Migrations are versioned Ruby files that describe changes to the database schema — creating tables, adding columns, adding indexes — in a database-agnostic way. Running rails db:migrate applies pending migrations in order and records which have run in a schema_migrations table, so the schema can evolve safely across environments and be rolled back with rails db:rollback if needed. Migrations keep a team's database schema in version control alongside the application code, which is essential for reproducible deployments.
Cricket analogy: Migrations are like the official ICC ground-preparation logbook: each pitch change (relaying turf, adjusting boundary rope) is a dated, ordered entry so any curator anywhere can reproduce the exact same pitch conditions.
# db/migrate/20260701120000_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.1]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body
t.references :author, null: false, foreign_key: { to_table: :users }
t.boolean :published, default: false
t.timestamps
end
add_index :articles, :title
end
end
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :author, class_name: "User"
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { maximum: 200 }
validates :body, presence: true
scope :published, -> { where(published: true) }
end
# Usage
recent = Article.published.where("created_at > ?", 1.week.ago).order(created_at: :desc)
article = Article.create!(title: "Rails 7 tips", body: "...", author: current_user)Associations
Associations like belongs_to, has_many, and has_and_belongs_to_many declare relationships between models and generate a rich set of methods automatically: article.author, author.articles, author.articles.build(title: "..."). The dependent: :destroy option ensures related records (comments, in the example above) are cleaned up when the parent is deleted, preventing orphaned rows. For many-to-many relationships, has_many :through is generally preferred over has_and_belongs_to_many because it allows the join model to carry its own attributes and validations.
Cricket analogy: has_many is like a captain's relationship to the squad: captain.players lists the team, player.captain points back, and dependent: :destroy is like disbanding the whole squad's contracts if the captaincy is vacated so no player is left in limbo.
Validations and callbacks
Validations run before a record is saved and prevent invalid data from reaching the database — validates :email, presence: true, uniqueness: true is a common pattern. Callbacks such as before_save, after_create, and after_commit hook into the object's lifecycle to perform side effects like sending a welcome email or normalizing data. Callbacks are powerful but should be used sparingly; overusing them scatters business logic across many small hooks and makes the flow of a single save call hard to trace.
Cricket analogy: Validations are like the umpire checking a bowler's run-up is legal before the ball is bowled -- validates :email, presence: true is the no-ball check that stops invalid deliveries, while callbacks like after_create are like the scoreboard automatically updating after a valid run is confirmed.
ActiveRecord's query interface is lazy: Article.where(published: true) does not hit the database until the relation is enumerated (via .each, .to_a, rendering in a view, etc). This lets you chain scopes like .where(...).order(...).limit(...) and have Rails combine them into a single efficient SQL query.
The N+1 query problem is one of the most common ActiveRecord performance bugs: looping over articles and calling article.author.name inside the loop issues one query per article. Use Article.includes(:author) to eager-load the association and collapse it into two queries total.
- ActiveRecord maps each model class to a table and each instance to a row, translating Ruby method calls into SQL.
- Migrations version-control schema changes and can be applied or rolled back with
rails db:migrate/db:rollback. - Associations (
belongs_to,has_many,has_many :through) generate convenience methods for related records. - Validations run before save and reject invalid data; callbacks hook into the object lifecycle for side effects.
- ActiveRecord queries are lazy and chainable, letting scopes combine into a single SQL statement.
- Eager loading with
includesprevents the N+1 query problem when accessing associations in a loop.
Practice what you learned
1. What does a single ActiveRecord model instance represent?
2. What command applies pending database migrations?
3. Which association type is preferred over `has_and_belongs_to_many` when the join table needs extra attributes?
4. What problem does `Article.includes(:author)` solve?
5. Are ActiveRecord relations evaluated eagerly or lazily?
Was this page helpful?
You May Also Like
Intro to Ruby on Rails
An overview of the Rails web framework, its MVC architecture, and the convention-over-configuration philosophy that lets developers build database-backed apps quickly.
Classes and Objects in Ruby
Learn how Ruby defines classes as blueprints for objects, covering the `class` keyword, `initialize`, instance methods, and how everything in Ruby is an object.
Exception Handling in Ruby
Understand Ruby's begin/rescue/ensure/raise vocabulary for handling errors gracefully, including exception hierarchies, retry, and best practices for robust code.
Enumerable Deep Dive
Go beyond map and select to explore Enumerable's full toolkit — sort_by, group_by, partition, each_with_object, lazy enumerators, and more — for expressive collection processing.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics