Models and tests: the code-along

code-along-complete/ in your copy of models-and-tests has every step from class,
finished. Steps 0, 1 and 4 are single files. Steps 2, 3 and 5 changed files across the app, so each is a folder that mirrors it, with a README saying which files to compare with yours.

This post is the map: what each step built, why it's there, and where the longer explanation lives. Read it beside the finished files.

0. The check: 0-replay.rb

Six games of craps, each a list of rolls and the result the rules say it must have. The game was already in the file, commented out. You wrote the check first, ran it, watched it fail (FAIL [7]: expected YOU WIN!, got not played yet), then uncommented the game and saw six PASS lines.

That order is the week's first idea. A check you have never seen fail might not be checking anything, and the three live edits proved it: break the game and the check catches it; change == to = and the check passes everything, including a broken game. The name for working this way is test-driven development.

Explained in full, with the same code: Programming Fundamentals - Lab 7 - Solution. The only differences are that the class file had the game pre-written and commented out, and the PASS line prints the rolls only.

1. Classes: 1-classes.rb

Every value has a class, and the class decides what it can do. "tacos".upcase, 7.odd?. Then a class of our own, a Dog that is a Hash (so it can hold a name) and can also speak:

class Dog < Hash
  def speak
    "woof"
  end
end

daisy = Dog.new
daisy["name"] = "Daisy"
puts daisy.speak   # woof

The step is there for one sentence at its end: a model is a class for one database table. Company.new makes one row the way Dog.new makes one dog; company["name"] reads a column the way daisy["name"] reads a key; and Company.all and Company.where are methods called on the class itself, which ask the table for rows that already exist.

The longer version: Programming Fundamentals - Classes. It was written for an older repo, so its repository link and file names are out of date; the Ruby is the same.

2. A table for companies: 2-migrations/

One migration, written by hand, and an empty model.

bin/rails generate model Company
create_table :companies do |t|
  t.string "name"
  t.string "city"
  t.string "state"
  t.string "url"

  t.timestamps
end
bin/rails db:migrate

A migration is the CREATE TABLE from week 3, as a versioned Ruby file in db/migrate/ that Rails runs in order. db/schema.rb is what Rails wrote after running it; never edit it. bin/reset-db rebuilds the database from the migrations and deletes every row, for when a migration is wrong or the database is in a state you can't explain.

The longer version: Migrations.

3, part 1. Use it: 3-companies/scripts/list-companies.rb

The first file the agent wrote. It deletes every company, saves three, finds one by name and changes its url, and prints them all:

apple = Company.new
apple["name"] = "Apple"
apple["city"] = "Cupertino"
apple.save

company = Company.find_by({ "name" => "Anthropic" })
company["url"] = "https://claude.ai"
company.save

for company in Company.all
  puts "#{company["name"]}, #{company["city"]}, #{company["url"]}"
end

app/models/company.rb was still empty when this ran. Saving, finding and changing came with the table. ActiveRecord wrote the SQL (INSERT, SELECT ... WHERE "name" = ..., UPDATE) and logged it in log/development.log.

The longer version of new, save, all, where and find_by: Models.

3, part 2 and 4. The rules, the tests, and the helpful change

DONE.md, one sentence per rule; a test for each sentence, run red before the model changes; your check that every sentence got its test; one validates line that makes them green; and then a "helpful" change to the model that the app accepts and one test refuses.

Explained in full: The definition of done, and tests.

5. Contacts: 5-contacts/

A second table that holds the first table's id, and the two lookups that make a
relationship: Contact.where({ "company_id" => apple["id"] }) from a company to its people, Company.find_by({ "id" => contact["company_id"] }) from a person to their company. No has_many, no belongs_to, on purpose.

Explained in full: Related rows, with where.

Comparing with your own copy

Your agent's tests and scripts came from your sentences, so they won't match these line for line. The things that should match: every sentence in your DONE.md has a test named with it, each test sets up the situation its sentence describes, the tests failed before the model changed, and they pass now. If you copy a file by hand, never copy a migration for a table you already have; compare it with yours instead.

The practice labs in labs/ carry on from here: two more tables by hand, then salespeople and activities from your own rules, then a change of your own to catch. Solutions:
lab 1,
lab 2,
lab 3,
lab 4.