The problem

From the Practice section of the web-apps README:

Contacts. Make a page at /contacts that lists every contact's first and last name and the name of their company. Look the company up with Company.find_by({ "id" => contact["company_id"] }). Then add each contact's email.

It's the companies page from class, with one new thing: a contact doesn't hold its company's name, only its company's id.

1. The route and the controller

In config/routes.rb, under the companies line:

get("/contacts", { :controller => "contacts", :action => "index" })

In the terminal that isn't running the server:

bin/rails generate controller contacts

And the action, in app/controllers/contacts_controller.rb:

class ContactsController < ApplicationController
  def index
    render({ :template => "contacts/index" })
  end
end

The errors on the way are the tacos ones: no controller, unknown action, missing template.

2. Every contact's name

app/views/contacts/index.html.erb. The same loop as the companies page, over Contact.all:

<h1>Contacts</h1>
<ul>
  <% for contact in Contact.all %>
    <li><%= contact["first_name"] %> <%= contact["last_name"] %></li>
  <% end %>
</ul>

Four names: Tim Cook, Craig Federighi, Andy Jassy, Dario Amodei.

3. The company, by its id

Open /data and look at the contacts table. Tim Cook's company_id is 1, and the company whose id is 1 is Apple. So for each contact, find the company whose id is that contact's company_id:

<% company = Company.find_by({ "id" => contact["company_id"] }) %>

find_by gives back one row, the first that matches, so company["name"] is its name. It goes inside the loop, because each contact has its own company:

<h1>Contacts</h1>
<ul>
  <% for contact in Contact.all %>
    <% company = Company.find_by({ "id" => contact["company_id"] }) %>
    <li>
      <%= contact["first_name"] %> <%= contact["last_name"] %>, <%= company["name"] %>, <%= contact["email"] %>
    </li>
  <% end %>
</ul>

The email was the last step: contact["email"], one more cell of the same row.

Tim Cook, Apple, tim@apple.com
Craig Federighi, Apple, craig@apple.com
Andy Jassy, Amazon, andy@amazon.com
Dario Amodei, Anthropic, dario@anthropic.com

What the log shows

Refresh the page and look at the bottom of log/development.log. One SELECT for the contacts, then one more for each contact's company, with a WHERE "companies"."id" = ? in it: five lines in all. The second Apple one starts with CACHE: Craig's company is Tim's, and Rails remembered the answer from a moment before, inside the same request. Every other find_by is its own trip to the database. For four contacts that's nothing. For four thousand it would be slow, and later in the course you'll see ways to ask for all of it at once. For now, the point is that you can read exactly what your page asked the database for.

When it went wrong

  • undefined method '[]' for nil. find_by found nothing, so company is nil. Usually a typo in the column: "company_id", with the underscore. (Or a contact whose company was deleted.)
  • Every contact shows the same company. The find_by line is above the loop instead of inside it.
  • company.name instead of company["name"]. It works, but stay with the brackets: a row is read the same way everywhere in this course, column name in brackets.