Active Record Basics
Active Record turns a table row into a Ruby object so create, read, update, and delete stay in Ruby instead of hand-written SQL.
<!-- hal:authoritative:yaml -->
Active Record turns a table row into a Ruby object so create, read, update, and delete stay in Ruby instead of hand-written SQL.
§I — Frame
Duha session 02 for the Rails track. Dual-fire beside Rust. The spine remains the official Rails Guides snapshot on disk: rails-guides/ at v8.1.3.1. Today's file is active_record_basics.html.
Session 01 built a Product model, migrated products, and ran console create/read/update/delete. That fire treated Active Record as the model half of MVC. This fire opens the dedicated Guides page: what the pattern is, which naming and schema conventions make the empty model class work, how ApplicationRecord wires the map, and the CRUD API the guide documents for ordinary rows.
By the end, name the naming and schema conventions, create a model that maps to a table, and perform create/read/update/delete through Active Record without raw SQL. Validations, callbacks, migrations, and associations appear only as pointers to later syllabus rows.
§II — What Active Record is
Active Record is the M in MVC for database-backed data: Ruby objects whose attributes need persistent storage. The guide also names two related ideas.
The Active Record pattern (Martin Fowler): an object that wraps a row in a database table, encapsulates database access, and adds domain logic on that data. The object carries both state and behavior. Class shape tracks table shape closely enough that reading and writing rows feels like ordinary Ruby.
Object Relational Mapping (ORM): connect rich language objects to relational tables so attributes (and later associations) store and load without writing SQL for every step. Active Record is Rails' ORM framework. With it you:
- represent models and their data
- represent associations between models (later session)
- represent inheritance hierarchies through related models
- validate models before they persist (later session)
- perform database operations in an object-oriented style
Active Model covers Ruby objects that do not need a database table. Both sit in the M of MVC. Today stays on Active Record: models backed by tables.
Session 01's empty Product class already used this machinery. Rails read column names from products and exposed them as attributes. This guide explains why that worked.
§III — Naming conventions
Convention over Configuration is the rule: if most apps configure the same way, that way becomes the default. Follow Active Record's naming map and you write little or no mapping code.
Rails pluralizes the model class name to find the table. Class Book maps to table books. Multi-word classes use UpperCamelCase; tables use snake_case plurals:
| Model / Class | Table / Schema |
|---|---|
Article | articles |
LineItem | line_items |
Product | products |
Person | people |
BookClub | book_clubs |
Session 01 already used this map: singular Product, plural products. Irregular plurals (Person → people) go through Active Support's inflector. Stay inside English pluralization and the generator and the runtime agree.
§IV — Schema conventions
Column names carry meaning too.
- Primary key — by default an integer column named
id(biginton PostgreSQL/MySQL/MariaDB,integeron SQLite). Migrations create it for you. - Foreign keys —
singularized_table_name_id(order_id,line_item_id). Associations look for these names later.
Optional reserved columns add framework behavior:
created_at/updated_at— set automatically on create and update (t.timestampsin a migration)lock_version— optimistic lockingtype— Single Table Inheritance(association_name)_type— polymorphic associations(table_name)_count— cached association counts (for examplecomments_countonarticles)
Those names are reserved. Avoid naming an ordinary attribute type unless you mean STI. Session 01's products table already had id, name, created_at, and updated_at from that convention set.
§V — Creating models and ApplicationRecord
rails new writes app/models/application_record.rb. ApplicationRecord subclasses ActiveRecord::Base and is the base class for every model in the app. A new model is a subclass:
# app/models/book.rb
class Book < ApplicationRecord
end
That maps Book to books. Each column becomes an attribute on instances. You do not list columns in the class file; the schema supplies them at runtime.
Create the table with a migration, not raw SQL, in normal Rails work:
bin/rails generate model Book title:string author:string
bin/rails db:migrate
The generator writes app/models/book.rb, a CreateBooks migration under db/migrate/, and test fixtures. The migration (Rails 8.1) looks like:
class CreateBooks < ActiveRecord::Migration[8.1]
def change
create_table :books do |t|
t.string :title
t.string :author
t.timestamps
end
end
end
id arrives by convention. t.timestamps adds created_at and updated_at. After migrate, attributes work immediately:
book = Book.new
book.title = "The Hobbit"
book.title
# => "The Hobbit"
This is the same empty-class pattern as session 01's Product. Swap the names and you are on the Getting Started store again.
Namespaced models (Book::Order under app/models/book/order.rb) exist when you want folders under app/models. The guide covers table_name_prefix for that case. Skip the namespace path until you need it; the default flat model is enough for CRUD practice.
§VI — Overriding conventions (briefly)
Legacy schemas and odd table names are supported. Override the table:
class Book < ApplicationRecord
self.table_name = "my_books"
end
Override the primary key:
class Book < ApplicationRecord
self.primary_key = "book_id"
end
Fixture class mapping needs a matching tweak when the table name changes. Prefer the defaults for new tables. Overrides exist for the exception, not as day-two style.
§VII — CRUD: create, read, update, delete
CRUD is create, read, update, delete. Active Record builds methods that emit SQL underneath. You call Ruby; the adapter talks to the database.
Create
new builds an unsaved object. save persists it. create builds and saves in one step:
book = Book.create(title: "The Lord of the Rings", author: "J.R.R. Tolkien")
# id is assigned once the row is committed
book = Book.new
book.title = "The Hobbit"
book.author = "J.R.R. Tolkien"
book.save
book.id
# => assigned after save
Both new and create accept a block for initialization. Prefer create / save for ordinary rows. The guide also mentions insert / insert_all for bulk inserts that skip validations and callbacks; leave those for later when you know you need them.
Read
Book.all
Book.first
Book.last
Book.take
Book.find(42) # raises if missing
Book.find_by(title: "The Hobbit") # nil if missing
Book.where(author: "Douglas Adams").order(created_at: :desc)
find looks up by primary key and raises when the row is absent. find_by returns the first match or nil. where returns a relation (lazy query object), same family as session 01's Product.where. Deeper query chaining belongs to the Query Interface guide (syllabus row 7).
Update
Load a record, change attributes, persist:
book = Book.find_by(title: "The Lord of the Rings")
book.title = "The Lord of the Rings: The Fellowship of the Ring"
book.save
# or
book.update(title: "The Lord of the Rings: The Fellowship of the Ring")
update assigns and saves. updated_at advances automatically when timestamps are present. Bulk update_all skips validations and callbacks; treat it as a special tool.
Delete
book = Book.find_by(title: "The Lord of the Rings")
book.destroy
destroy runs callbacks and removes the row. destroy_by / destroy_all remove sets. delete / delete_all skip callbacks. Prefer destroy until you have a reason to skip the object lifecycle.
Tie it back to the store from session 01: Product.create, Product.find / find_by, product.update, product.destroy are the same verbs on a different class.
§VIII — Forward pointers only
The guide closes with four topics this session does not deepen:
- Validations — check state before write;
save/create/updatereturn false (or bang forms raise) when invalid. Syllabus row 4. - Callbacks — hooks around the object lifecycle (before/after save, and related). Syllabus row 5.
- Migrations — versioned schema changes (
create_table,add_column, and friends). You already ran one in session 01; row 3 owns the full guide. - Associations — declare relationships (
has_many,belongs_to, and the rest) once foreign keys follow*_id. Syllabus row 6.
Name them so the map is complete. Do not implement them here beyond what session 01 already showed for a single presence validation.
§IX — One complete proof
In the session 01 store app (or a fresh rails new store):
bin/rails generate model Book title:string author:string
bin/rails db:migrate
bin/rails console
Inside the console:
Book.create!(title: "The Hobbit", author: "J.R.R. Tolkien")
Book.create!(title: "Dune", author: "Frank Herbert")
Book.find_by(title: "Dune").author
# => "Frank Herbert"
b = Book.find_by(title: "The Hobbit")
b.update!(title: "The Hobbit (illustrated)")
Book.where(author: "J.R.R. Tolkien").pluck(:title)
b.destroy
Book.count
# => 1
No raw SQL appears in that script. Active Record issued INSERT, SELECT, UPDATE, and DELETE for you.
That meets session 02 done-criteria: naming and schema conventions named, a model mapped to a table, create/read/update/delete performed through Active Record.
§X — Closing
Active Record is both a Fowler pattern and Rails' ORM. Convention maps Product → products and expects id, *_id foreign keys, and optional timestamps. ApplicationRecord is the app-wide base; an empty subclass is enough when the table follows convention. CRUD methods (new/save, create, find/find_by, update, destroy) are the daily verbs. Validations, callbacks, migrations, and associations wait on their own Guides rows.
Name the hinge when Book.create returns an object whose id is set and whose created_at is filled without you writing those columns into the call: the schema convention and the ORM agreed on the row shape.
Session 03 is Guides Active Record Migrations. Stop here.
Examine well. State the class↔table naming rule, list the default primary key and foreign-key patterns, generate a model, migrate, create two rows, find one, update a field, destroy a row, and confirm the count without writing SQL.
Related
- Syllabus: Duha Rails syllabus, session 02
- Grounding tome: Rails Guides — Active Record Basics
- Snapshot pin:
09-Tomes/Polyglot-Dev/Backend-Stack/Ruby/rails-guides/VERSION.txt→ v8.1.3.1 - Live twin: https://guides.rubyonrails.org/active_record_basics.html
- Prior fire: Duha Rails session 01, Getting Started
- Next fire: session 03, Guides Active Record Migrations (unwritten)