Models and Tests - Lab 1 - Solution

The problem

From labs/1-migrations.rb in your copy of models-and-tests:

The CRM has companies and contacts. The sales team needs two more things: the salespeople themselves, and the activities they log with contacts (a call, a meeting, a note). crm-schema.sql has all four tables in SQL; use it as your reference.

Write these two migrations yourself, no agent, the way you wrote the one for companies.

This lab is the one you type by hand, so that the next three can be about the rules rather than the typing. Everything happens in the terminal and in the files Rails makes for you.

Salesperson

From crm-schema.sql:

CREATE TABLE salespeople (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  first_name TEXT,
  last_name TEXT,
  email TEXT
);

Make the model and its migration. The model's name is singular and capitalized:

bin/rails generate model Salesperson

Rails makes two files, and names the table itself:

create    db/migrate/20261021151226_create_salespeople.rb
create    app/models/salesperson.rb

The table is salespeople, not salespersons. Rails knows how to pluralize, and it had an opinion about this one. That is the answer to step 2, and it is why the model is always named in the singular: you name the thing, Rails names the table of them.

Open the migration. Inside the create_table block, add one line per column from the SQL. Leave out id: every table gets one without asking.

class CreateSalespeople < ActiveRecord::Migration[8.1]
  def change
    create_table :salespeople do |t|
      t.string "first_name"
      t.string "last_name"
      t.string "email"

      t.timestamps
    end
  end
end

t.timestamps was already there. It adds created_at and updated_at, which Rails fills in for you on every save. The SQL file doesn't have them; that's fine.

Activity

CREATE TABLE activities (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  salesperson_id INTEGER,
  contact_id INTEGER,
  note TEXT
);

Same steps:

bin/rails generate model Activity
class CreateActivities < ActiveRecord::Migration[8.1]
  def change
    create_table :activities do |t|
      t.integer "salesperson_id"
      t.integer "contact_id"
      t.string "note"

      t.timestamps
    end
  end
end

The two id columns are integers, because they hold another table's id, which is an integer. That one column is the whole relationship between the tables: an activity knows which salesperson and which contact it belongs to by holding their ids, the same way a foreign key did in SQL. Lab 3 uses them.

Run them

bin/rails db:migrate
== 20261021151226 CreateSalespeople: migrating ================================
-- create_table(:salespeople)
== 20261021151226 CreateSalespeople: migrated (0.0010s) =======================

== 20261021151227 CreateActivities: migrating =================================
-- create_table(:activities)
== 20261021151227 CreateActivities: migrated (0.0008s) ========================

Rails runs only the migrations it hasn't run yet, in order by their number. The number is the date and time you generated the file, so yours won't match these.

Check db/schema.rb

Rails wrote it from the migrations. Never edit it by hand; it is the record of what the migrations did.

create_table "activities", force: :cascade do |t|
  t.integer "contact_id"
  t.datetime "created_at", null: false
  t.string "note"
  t.integer "salesperson_id"
  t.datetime "updated_at", null: false
end

create_table "salespeople", force: :cascade do |t|
  t.datetime "created_at", null: false
  t.string "email"
  t.string "first_name"
  t.string "last_name"
  t.datetime "updated_at", null: false
end

The columns are listed alphabetically, with the timestamps mixed in. The order doesn't matter. What matters is that every column in crm-schema.sql is there with the right type, and it is.

If something went wrong

You missed a column, and already ran the migration. Add the line to the migration file, then run bin/reset-db. It rebuilds the database from every migration in db/migrate/ and deletes every row. Your files stay as they are.

table "salespeople" already exists. There are two migrations that create it, probably because you ran generate model twice. Delete the newer one in db/migrate/ and run bin/reset-db.

The model file is empty. It should be. class Salesperson < ApplicationRecord and end is all a model needs to save, find and change rows. Lab 2 adds the rules.