Hedronite Lesson · Polyglot-Dev / Rails · Tue 2026-09-08

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.

Lesson Class: Duha (Rails Guides track)
Focus: ORM · naming · schema conventions · ApplicationRecord · CRUD
Code Blocks: clean blocks, explanation in prose
Done-criteria: name conventions, map a model to a table, CRUD without raw SQL
Grounding: rails-guides/active_record_basics.html · VERSION.txt v8.1.3.1 · Flanagan not cited
The pattern
A row becomes an object with data and behavior.
The convention
Product maps to products. id and *_id are expected.
The verbs
create, find, update, destroy — Active Record speaks SQL for you.
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:

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 / ClassTable / Schema
Articlearticles
LineItemline_items
Productproducts
Personpeople
BookClubbook_clubs

Session 01 already used this map: singular Product, plural products. Irregular plurals (Personpeople) 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.

Optional reserved columns add framework behavior:

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:

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 Productproducts 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