<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[ENTR-451]]></title><description><![CDATA[ENTR-451: Introduction to Software Development, Kellogg School of Management]]></description><link>https://entr451.com/</link><image><url>https://entr451.com/favicon.png</url><title>ENTR-451</title><link>https://entr451.com/</link></image><generator>Ghost 4.20</generator><lastBuildDate>Tue, 10 Nov 2026 17:40:03 GMT</lastBuildDate><atom:link href="https://entr451.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Opening and reviewing a pull request]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In week one you added one file to the class roster and it went in through a pull request. That was the smallest possible change, in a shared repository, with the computer refusing to let anyone skip the review. From here on the change is a feature, the repository is</p>]]></description><link>https://entr451.com/opening-and-reviewing-a-pull-request/</link><guid isPermaLink="false">6aead120d9019e19441c3586</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 04 Nov 2026 06:33:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In week one you added one file to the class roster and it went in through a pull request. That was the smallest possible change, in a shared repository, with the computer refusing to let anyone skip the review. From here on the change is a feature, the repository is yours, and nobody enforces the rule but you. The steps are the same.</p>
<h2 id="1-start-from-a-ticket">1. Start from a ticket</h2>
<p>A ticket is a GitHub issue: a short story about who wants what and why, and under it a definition of done, a few sentences that say when the feature is finished. In the class repo the tickets are already in the <strong>Issues</strong> tab. For your own project you write them: one issue per feature, three to five checkable sentences each.</p>
<p>Read the ticket before you touch anything. It is what your reviewer will hold the change against.</p>
<h2 id="2-branch-first">2. Branch first</h2>
<p>Never build on <code>main</code>. In the bottom-left corner of the Codespace, click <code>main</code>, choose <strong>Create new branch</strong>, type <code>posts-form</code>, and press Enter. The corner now reads <code>posts-form</code>, and everything you commit from here goes on that branch. (Source Control&apos;s <strong>...</strong> menu has the same thing under <strong>Branch</strong>, <strong>Create Branch</strong>.)</p>
<p>The name describes the change. If you forget and commit on <code>main</code>, nothing is lost: make the branch now the same way, and your commit comes with it.</p>
<h2 id="3-build-it-with-tests">3. Build it, with tests</h2>
<p>Before the prompt, one line in <code>AGENTS.md</code>, the file your agent reads before it does anything. It already says that every sentence of a definition of done gets a test, and it points at the code rules; the agent already knows how to read a GitHub issue. Add the one thing nobody said, at the end of the Rules list:</p>
<pre><code>- After changing any code, run the tests and report the result before saying you are done.
</code></pre>
<p>Then the prompt can be short, because the file carries the rest:</p>
<pre><code>Read issue #1 and build it.
</code></pre>
<p>Watch what happens that you did not ask for: the agent reads the ticket, writes one named test per sentence, runs them and reports. That is what the file is for. Anything you would otherwise type into every prompt belongs there instead.</p>
<p>Then check it yourself, twice: use the page in the browser, and run the tests:</p>
<pre><code>bin/rails test
</code></pre>
<p>Open the test file and read the test names against the ticket. One per sentence? A sentence with no test is a sentence nobody checked.</p>
<h2 id="4-commit-and-publish">4. Commit and publish</h2>
<p>In Source Control, stage the changes and commit with a message that says what changed, so that someone reading the history later knows without opening the diff. &quot;Add the new post form and create action, with tests&quot; is a message. &quot;done&quot; and &quot;tried again&quot; are not.</p>
<p>Then <strong>Publish Branch</strong>. Your branch is now on GitHub; <code>main</code> is untouched.</p>
<h2 id="5-open-the-pull-request">5. Open the pull request</h2>
<p>On your repo&apos;s GitHub page a banner offers <strong>Compare &amp; pull request</strong>. Click it. The title is the ticket&apos;s title. In the body, write <code>closes #1</code> (the issue number), so merging the PR closes the ticket, and a sentence on what you did. <strong>Create pull request</strong>.</p>
<p>A pull request is a request to change <code>main</code>. Nothing has changed yet.</p>
<h2 id="6-invite-your-reviewer">6. Invite your reviewer</h2>
<p>Your copy of the repo is private, so only a collaborator can review it. On the repo page: <strong>Settings</strong>, <strong>Collaborators</strong>, <strong>Add people</strong>, your partner&apos;s GitHub username, <strong>Add</strong>. They accept from the email GitHub sends, or at github.com/notifications.</p>
<h2 id="7-review-someone-elses">7. Review someone else&apos;s</h2>
<p>Open their pull request. Read the issue it closes, then click <strong>Files changed</strong> and read the diff. If you are sitting next to them, run the tests on their machine:</p>
<pre><code>bin/rails test
</code></pre>
<p>Then answer five questions:</p>
<ol>
<li>Does the change do what the issue says? Read the issue, then the diff.</li>
<li>Does the commit message tell you what changed without opening the diff?</li>
<li>Does it follow the pattern? The right actions in the right controller, the form posts to the resource&apos;s path, <code>create</code> redirects.</li>
<li>Is there a test for every sentence in the issue, and do they pass when you run them?</li>
<li>Would you merge it? If not, what would you ask for?</li>
</ol>
<p>The first, second and fifth are week one&apos;s three questions. They never change. The third and fourth are new because the change is code now, built against a pattern and a ticket.</p>
<p>Leave at least one comment on a line: hover over a line in the diff, click the blue <strong>+</strong>, write it, <strong>Add single comment</strong>. Then <strong>Review changes</strong>. If all five answers are yes, <strong>Approve</strong>. If not, <strong>Request changes</strong> and say which question failed. <strong>Submit review</strong>.</p>
<h2 id="8-merge">8. Merge</h2>
<p>When your reviewer approves, <strong>Merge pull request</strong>, <strong>Confirm merge</strong>, <strong>Delete branch</strong>. The feature is on <code>main</code>, and the issue it closed is closed.</p>
<p>The merge button worked before anyone reviewed. On a personal repo, GitHub does not stop you. The rule held because you held it. On a team, the repository is set up so the computer holds it too, which is what the roster did in week one.</p>
<h2 id="if-you-requested-changes">If you requested changes</h2>
<p>The author fixes it on the same branch, commits, and syncs. The pull request updates on its own. Review again. That loop, change, review, change, merge, is the part of this course that carries the furthest, and the one an agent cannot do for you: someone who did not write the code has to read it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Apps 2 - Issue 1 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-ticket">The ticket</h2>
<p>Issue #1 in your copy of <code>web-apps-2</code>:</p>
<blockquote>
<p><strong>Post a taco</strong><br>
As a Tacostagram user, I want to post a photo of my tacos with a caption, so everyone can see what I&apos;m eating.</p>
</blockquote>
<blockquote>
<p><strong>Definition of done</strong></p>
<ul>
<li>The new post page shows a form with fields for</li></ul></blockquote>]]></description><link>https://entr451.com/web-apps-2-posts-solution/</link><guid isPermaLink="false">6aeaca56d9019e19441c3561</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 04 Nov 2026 06:05:15 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-ticket">The ticket</h2>
<p>Issue #1 in your copy of <code>web-apps-2</code>:</p>
<blockquote>
<p><strong>Post a taco</strong><br>
As a Tacostagram user, I want to post a photo of my tacos with a caption, so everyone can see what I&apos;m eating.</p>
</blockquote>
<blockquote>
<p><strong>Definition of done</strong></p>
<ul>
<li>The new post page shows a form with fields for the author, the body and the image.</li>
<li>Submitting the form creates a post with the author, body and image that were entered.</li>
<li>After submitting, the user lands on the posts page and sees the new post there.</li>
</ul>
</blockquote>
<p>The ticket is the whole specification. Your agent read it, built it, and wrote one test per sentence. Yours will not match this line for line, and should not. What should match is the shape: two actions, one form, three tests, and a pull request that a classmate reviewed against the ticket.</p>
<h2 id="what-the-agent-built">What the agent built</h2>
<h3 id="the-controller">The controller</h3>
<p><code>new</code> and <code>create</code> were already there as comments. The agent filled them in, keeping each step comment:</p>
<pre><code class="language-ruby">def new
  # render posts/new view, the form for a new Post
  render({ :template =&gt; &quot;posts/new&quot; })
end

def create
  # start with a new Post
  post = Post.new
  # assign the form&apos;s values to the Post&apos;s columns
  post[&quot;author&quot;] = params[&quot;author&quot;]
  post[&quot;body&quot;] = params[&quot;body&quot;]
  post[&quot;image&quot;] = params[&quot;image&quot;]
  # save the Post row
  post.save
  # redirect the user
  redirect_to(&quot;/posts&quot;)
end
</code></pre>
<p><code>new</code> only renders the form. <code>create</code> has no view: it saves and sends the user to a page that already exists. The three keys in <code>params</code> are the three <code>name</code> attributes in the form below.</p>
<h3 id="the-form">The form</h3>
<p><code>app/views/posts/new.html.erb</code> had a skeleton with a comment where the fields go:</p>
<pre><code class="language-erb">&lt;h1&gt;New post&lt;/h1&gt;

&lt;form action=&quot;/posts&quot; method=&quot;post&quot;&gt;
  &lt;p&gt;
    &lt;label for=&quot;author&quot;&gt;Author&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;author&quot; id=&quot;author&quot;&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;label for=&quot;body&quot;&gt;Body&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;body&quot; id=&quot;body&quot;&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;label for=&quot;image&quot;&gt;Image&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;image&quot; id=&quot;image&quot;&gt;
  &lt;/p&gt;
  &lt;button&gt;Post it&lt;/button&gt;
&lt;/form&gt;

&lt;p&gt;&lt;a href=&quot;/posts&quot;&gt;Back to all posts&lt;/a&gt;&lt;/p&gt;
</code></pre>
<p><code>action=&quot;/posts&quot;</code> and <code>method=&quot;post&quot;</code> are the <code>create</code> row of the pattern. No routes were added: <code>resources(&quot;posts&quot;)</code> in <code>config/routes.rb</code> already made all seven.</p>
<h3 id="the-tests">The tests</h3>
<p>One per sentence, named with the sentence, in <code>test/integration/posts_page_test.rb</code>:</p>
<pre><code class="language-ruby">require &quot;test_helper&quot;

class PostsPageTest &lt; ActionDispatch::IntegrationTest
  test &quot;The new post page shows a form with fields for the author, the body and the image.&quot; do
    get(&quot;/posts/new&quot;)

    assert_response(:success)
    assert_select(&quot;form[action=&apos;/posts&apos;][method=&apos;post&apos;]&quot;) do
      assert_select(&quot;input[name=&apos;author&apos;]&quot;)
      assert_select(&quot;input[name=&apos;body&apos;]&quot;)
      assert_select(&quot;input[name=&apos;image&apos;]&quot;)
    end
  end

  test &quot;Submitting the form creates a post with the author, body and image that were entered.&quot; do
    post(&quot;/posts&quot;, :params =&gt; {
      &quot;author&quot; =&gt; &quot;Ben&quot;,
      &quot;body&quot; =&gt; &quot;Al pastor on a Tuesday&quot;,
      &quot;image&quot; =&gt; &quot;https://images.unsplash.com/photo-tacos&quot;
    })

    created = Post.find_by({ &quot;author&quot; =&gt; &quot;Ben&quot; })
    assert_not_nil(created)
    assert_equal(&quot;Al pastor on a Tuesday&quot;, created[&quot;body&quot;])
    assert_equal(&quot;https://images.unsplash.com/photo-tacos&quot;, created[&quot;image&quot;])
  end

  test &quot;After submitting, the user lands on the posts page and sees the new post there.&quot; do
    post(&quot;/posts&quot;, :params =&gt; {
      &quot;author&quot; =&gt; &quot;Ben&quot;,
      &quot;body&quot; =&gt; &quot;Al pastor on a Tuesday&quot;,
      &quot;image&quot; =&gt; &quot;https://images.unsplash.com/photo-tacos&quot;
    })

    assert_redirected_to(&quot;/posts&quot;)
    follow_redirect!
    assert_select(&quot;h3&quot;, &quot;Ben&quot;)
    assert_select(&quot;p&quot;, &quot;Al pastor on a Tuesday&quot;)
    assert_select(&quot;img[src=&apos;https://images.unsplash.com/photo-tacos&apos;]&quot;)
  end
end
</code></pre>
<pre><code>bin/rails test
3 runs, 21 assertions, 0 failures, 0 errors, 0 skips
</code></pre>
<p>Read each test against its sentence. The first fetches the form page and looks for the three inputs. The second submits the form and looks the row up in the table. The third submits, follows the redirect, and looks for the post on the page. Each one checks exactly what its sentence says, and nothing it doesn&apos;t.</p>
<h2 id="reviewing-it">Reviewing it</h2>
<p>The five questions from class, answered for this change:</p>
<ol>
<li><strong>Does it do what the issue says?</strong> The three sentences are the three tests, and they pass.</li>
<li><strong>Does the commit message tell you what changed?</strong> Yours to judge. &quot;Add the new post form and create action, with tests&quot; would.</li>
<li><strong>Does it follow the pattern?</strong> <code>new</code> and <code>create</code> in <code>PostsController</code>, the form posts to <code>/posts</code>, <code>create</code> redirects. Yes.</li>
<li><strong>A test per sentence, passing when you run them?</strong> Three sentences, three tests, green.</li>
<li><strong>Would you merge it?</strong> Yes. One thing you might ask for: the image is a text box the user has to paste an address into. That is what the ticket asked for; a file upload is week ten.</li>
</ol>
<h2 id="issue-2-if-you-got-to-it">Issue #2, if you got to it</h2>
<p>&quot;Add a contact to a company&quot; is the same loop with one new piece: the company&apos;s id has to travel from the company&apos;s page, through the form, to <code>create</code>, without the user typing it. That is a hidden field, and the &quot;Handling relationships&quot; section of <a href="https://entr451.com/creating-new-records/">Creating new records</a> walks through it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[From an address to a page: routes, controllers and views]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>A browser asks for an address, and a server sends back HTML. That&apos;s the whole of the web, and it hasn&apos;t changed since 1992. This post follows one request through a Rails app: the address you type, the three files Rails reads to answer it, and</p>]]></description><link>https://entr451.com/from-address-to-page/</link><guid isPermaLink="false">6ae118f3d9019e19441c3545</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 27 Oct 2026 21:36:53 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>A browser asks for an address, and a server sends back HTML. That&apos;s the whole of the web, and it hasn&apos;t changed since 1992. This post follows one request through a Rails app: the address you type, the three files Rails reads to answer it, and the page that comes back.</p>
<h2 id="addresses-host-names-and-paths">Addresses, host names and paths</h2>
<p>The thing in your browser&apos;s address bar is a URL (Uniform Resource Locator). Every URL has the same parts, which is the &quot;uniform&quot;:</p>
<ul>
<li>the protocol, <code>https</code></li>
<li>the colon-slash-slash, <code>://</code></li>
<li>the host name, like <code>entr451.com</code></li>
<li>the path, like <code>/tacos</code></li>
</ul>
<p>The &quot;locator&quot; part means each address points at one thing, one resource. You&apos;d expect these two addresses to show a page about books and a page about shoes:</p>
<pre><code>https://YourDomainName.com/books
https://YourDomainName.com/shoes
</code></pre>
<p>While you build, the protocol is always <code>https</code>, and the host name is the long address your Codespace gives port 3000. So from here on we only talk about the path. When this post says <code>/tacos</code>, it means &quot;add <code>/tacos</code> to the end of the address in that browser tab.&quot;</p>
<h2 id="the-one-route-thats-already-there">The one route that&apos;s already there</h2>
<p>Open <code>config/routes.rb</code> in your copy of <code>web-apps</code>. Ignoring the comment, it has one line:</p>
<pre><code class="language-ruby">Rails.application.routes.draw do
  get(&quot;/data&quot;, { :controller =&gt; &quot;data&quot;, :action =&gt; &quot;show&quot; })
end
</code></pre>
<p>Read it as a sentence: when someone asks for <code>/data</code>, run the <code>show</code> action of the data controller. That line points at two more files:</p>
<ul>
<li><code>app/controllers/data_controller.rb</code>, a class with a method named <code>show</code>. The method decides what to show.</li>
<li><code>app/views/data/show.html.erb</code>, the page itself.</li>
</ul>
<p>Visit <code>/data</code> and you see every table in the database and its rows. That&apos;s three files in three folders, and every page in a Rails app is the same three: a route, a controller, a view. Now we&apos;ll write a page of our own: a page about tacos, naturally.</p>
<h2 id="dont-fear-the-error">Don&apos;t fear the error</h2>
<p><strong>The route.</strong> Add a line under the data line:</p>
<pre><code class="language-ruby">Rails.application.routes.draw do
  # get(&quot;/things&quot;, { :controller =&gt; &quot;things&quot;, :action =&gt; &quot;index&quot; })

  get(&quot;/data&quot;, { :controller =&gt; &quot;data&quot;, :action =&gt; &quot;show&quot; })
  get(&quot;/tacos&quot;, { :controller =&gt; &quot;tacos&quot;, :action =&gt; &quot;index&quot; })
end
</code></pre>
<p>Save, and visit <code>/tacos</code>. An error:</p>
<pre><code>uninitialized constant TacosController
</code></pre>
<p>Error pages in Rails are loud and scary. Don&apos;t be afraid of them. They&apos;re there to help, and they often say exactly what to do next, if you stop and read them. This one says that Rails read your route, went looking for a tacos controller, and didn&apos;t find one. So that&apos;s the next file.</p>
<p><strong>The controller.</strong> Rails has a command that makes it. In a terminal other than the one running the server, run it with the name from the route:</p>
<pre><code>bin/rails generate controller tacos
</code></pre>
<p>It makes two things: the file <code>app/controllers/tacos_controller.rb</code>, and an empty folder, <code>app/views/tacos/</code>, which is where the view will go. You could type the file yourself, but the generator puts the file and the folder exactly where Rails looks for them, named the way it expects. Open the file:</p>
<pre><code class="language-ruby">class TacosController &lt; ApplicationController
end
</code></pre>
<p>An empty class. Refresh, and Rails says what&apos;s missing:</p>
<pre><code>The action &apos;index&apos; could not be found for TacosController
</code></pre>
<p>The route asked for the <code>index</code> action, and an action is a method in the controller. Add it:</p>
<pre><code class="language-ruby">class TacosController &lt; ApplicationController
  def index
    render({ :template =&gt; &quot;tacos/index&quot; })
  end
end
</code></pre>
<p>The class name matches the route&apos;s <code>:controller</code>, and the method name matches its <code>:action</code>. The method has one job tonight: show the template <code>tacos/index</code>. Refresh. Another error, and we&apos;re still not afraid:</p>
<pre><code>Missing template tacos/index
</code></pre>
<p>Rails found the controller, ran <code>index</code>, and went looking for the view. The next file.</p>
<p><strong>The view.</strong> Make <code>app/views/tacos/index.html.erb</code>, in the folder the generator made. The folder matters: it&apos;s <code>app/views/tacos/</code>, not <code>app/views/</code>. Put some HTML in it:</p>
<pre><code class="language-html">&lt;h1&gt;Tacos&lt;/h1&gt;
&lt;p&gt;Every filling on the menu.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;carnitas&lt;/li&gt;
  &lt;li&gt;al pastor&lt;/li&gt;
  &lt;li&gt;barbacoa&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Refresh, and there&apos;s the page. No more errors.</p>
<p>You didn&apos;t have to stop and start the server for any of this. Rails reads your files again on every request, so the next refresh always shows the latest save.</p>
<p>Each of those errors was Rails telling you where it got to. The route sends it to a controller, the controller runs an action, the action sends it to a view, and the error names the first piece that isn&apos;t there yet. That&apos;s worth remembering the next time a page doesn&apos;t work: refresh, read the error, make the piece it names.</p>
<h2 id="where-the-rest-of-the-html-came-from">Where the rest of the HTML came from</h2>
<p>Right-click the tacos page and choose View Page Source. You&apos;ll find something like this:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;web-apps&lt;/title&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;&gt;
    &lt;meta name=&quot;csrf-param&quot; content=&quot;authenticity_token&quot; /&gt;
    &lt;meta name=&quot;csrf-token&quot; content=&quot;...&quot; /&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/application-....css&quot; /&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;h1&gt;Tacos&lt;/h1&gt;
    &lt;p&gt;Every filling on the menu.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;carnitas&lt;/li&gt;
      &lt;li&gt;al pastor&lt;/li&gt;
      &lt;li&gt;barbacoa&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Whoa. We only typed what&apos;s inside <code>&lt;body&gt;</code>. The rest comes from<br>
<code>app/views/layouts/application.html.erb</code>, the application layout. Every page in an app shares the same outer shell (the <code>DOCTYPE</code>, <code>&lt;html&gt;</code>, <code>&lt;head&gt;</code>), so Rails keeps it in one file and puts each view inside it, where the layout says <code>&lt;%= yield %&gt;</code>.</p>
<h2 id="whats-next">What&apos;s next</h2>
<p>That was a long way to get what a plain HTML file gives you. But every piece is now in place: an address, a route, a controller and a view. A view isn&apos;t only HTML, though. Its file name ends in <code>.erb</code>, and that&apos;s the next post: <a href="https://entr451.com/two-great-things/">Two great things</a>, HTML and Ruby in the same file.</p>
<p>Fell behind in class? <code>code-along-complete/1-tacos/</code> in your copy has all three files, at the same paths as yours.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Apps - Cards - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From the Practice section of the <code>web-apps</code> README:</p>
<blockquote>
<p><strong>Cards.</strong> Make a page at <code>/cards</code> that deals a five-card hand from a shuffled deck, a new hand every time the page loads. Build the deck from these two arrays:</p>
<pre><code>ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10,</code></pre></blockquote>]]></description><link>https://entr451.com/web-apps-cards-solution/</link><guid isPermaLink="false">6ae11876d9019e19441c352a</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 27 Oct 2026 21:34:11 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From the Practice section of the <code>web-apps</code> README:</p>
<blockquote>
<p><strong>Cards.</strong> Make a page at <code>/cards</code> that deals a five-card hand from a shuffled deck, a new hand every time the page loads. Build the deck from these two arrays:</p>
<pre><code>ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, &quot;Jack&quot;, &quot;Queen&quot;, &quot;King&quot;, &quot;Ace&quot;]
suits = [&quot;Clubs&quot;, &quot;Diamonds&quot;, &quot;Hearts&quot;, &quot;Spades&quot;]
</code></pre>
<p>Show each card&apos;s name (&quot;Queen of Hearts&quot;). Then show its picture: <code>public/images/cards/</code> has one per card, named like <code>queen_of_hearts.svg</code>, and the page can show it with <code>&lt;img src=&quot;/images/cards/queen_of_hearts.svg&quot;&gt;</code>. Add a link that deals again.</p>
</blockquote>
<p>You&apos;ve dealt this hand before, in lab 5 of <code>ruby-intro</code>. That time it went to the terminal with <code>puts</code>; this time it goes on a page.</p>
<h2 id="1-the-route-and-the-controller">1. The route and the controller</h2>
<pre><code class="language-ruby">get(&quot;/cards&quot;, { :controller =&gt; &quot;cards&quot;, :action =&gt; &quot;index&quot; })
</code></pre>
<pre><code>bin/rails generate controller cards
</code></pre>
<pre><code class="language-ruby">class CardsController &lt; ApplicationController
  def index
    render({ :template =&gt; &quot;cards/index&quot; })
  end
end
</code></pre>
<h2 id="2-the-deck">2. The deck</h2>
<p>A loop inside a loop: for every rank, every suit. That&apos;s 13 times 4, 52 cards. Start with an empty array and push each card onto it:</p>
<pre><code class="language-erb">&lt;% ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, &quot;Jack&quot;, &quot;Queen&quot;, &quot;King&quot;, &quot;Ace&quot;] %&gt;
&lt;% suits = [&quot;Clubs&quot;, &quot;Diamonds&quot;, &quot;Hearts&quot;, &quot;Spades&quot;] %&gt;
&lt;% deck = [] %&gt;
&lt;% for rank in ranks %&gt;
  &lt;% for suit in suits %&gt;
    &lt;% card = { &quot;name&quot; =&gt; &quot;#{rank} of #{suit}&quot;, &quot;image&quot; =&gt; &quot;#{rank}_of_#{suit}&quot;.downcase } %&gt;
    &lt;% deck.push(card) %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
</code></pre>
<p>Each card needs two things: a name to show (&quot;Queen of Hearts&quot;) and the name of its picture (<code>queen_of_hearts</code>). They differ in two ways, the spaces and the capital letters, so a hash holds both, the way a row from the database holds its columns. <code>card[&quot;name&quot;]</code> and <code>card[&quot;image&quot;]</code> read them the way <code>company[&quot;name&quot;]</code> did.</p>
<p><code>&quot;#{rank}_of_#{suit}&quot;</code> is &quot;Queen_of_Hearts&quot;; <code>.downcase</code> makes it &quot;queen_of_hearts&quot;, the file&apos;s name. A number works too: <code>&quot;#{10}_of_Clubs&quot;.downcase</code> is &quot;10_of_clubs&quot;.</p>
<h2 id="3-shuffle-then-deal-five">3. Shuffle, then deal five</h2>
<pre><code class="language-erb">&lt;% hand = deck.shuffle[0, 5] %&gt;
</code></pre>
<p><code>shuffle</code> gives back the deck in a random order. <code>[0, 5]</code> takes five cards, starting from the first. Every refresh is a new request, so the view runs again and the deck is shuffled again.</p>
<p>This is why the hand comes from a shuffled deck, instead of picking five cards with <code>rand</code> the way the dice did: a deck has one Queen of Hearts. Five <code>rand</code> picks could pick the same card twice; five cards off the top of a shuffled deck can&apos;t. Two dice can match. Two cards in one hand can&apos;t.</p>
<h2 id="4-the-hand-on-the-page">4. The hand on the page</h2>
<pre><code class="language-erb">&lt;h1&gt;Cards&lt;/h1&gt;
&lt;p&gt;
  &lt;% for card in hand %&gt;
    &lt;img src=&quot;/images/cards/&lt;%= card[&quot;image&quot;] %&gt;.svg&quot; width=&quot;100&quot; alt=&quot;&lt;%= card[&quot;name&quot;] %&gt;&quot;&gt;
  &lt;% end %&gt;
&lt;/p&gt;
&lt;ul&gt;
  &lt;% for card in hand %&gt;
    &lt;li&gt;&lt;%= card[&quot;name&quot;] %&gt;&lt;/li&gt;
  &lt;% end %&gt;
&lt;/ul&gt;
&lt;a href=&quot;/cards&quot;&gt;Deal again&lt;/a&gt;
</code></pre>
<p>The <code>alt</code> text is what a screen reader says for the picture, and what the browser shows if the picture can&apos;t load. The card&apos;s name is exactly right for it.</p>
<h2 id="the-whole-view">The whole view</h2>
<pre><code class="language-erb">&lt;% ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, &quot;Jack&quot;, &quot;Queen&quot;, &quot;King&quot;, &quot;Ace&quot;] %&gt;
&lt;% suits = [&quot;Clubs&quot;, &quot;Diamonds&quot;, &quot;Hearts&quot;, &quot;Spades&quot;] %&gt;
&lt;% deck = [] %&gt;
&lt;% for rank in ranks %&gt;
  &lt;% for suit in suits %&gt;
    &lt;% card = { &quot;name&quot; =&gt; &quot;#{rank} of #{suit}&quot;, &quot;image&quot; =&gt; &quot;#{rank}_of_#{suit}&quot;.downcase } %&gt;
    &lt;% deck.push(card) %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
&lt;% hand = deck.shuffle[0, 5] %&gt;
&lt;h1&gt;Cards&lt;/h1&gt;
&lt;p&gt;
  &lt;% for card in hand %&gt;
    &lt;img src=&quot;/images/cards/&lt;%= card[&quot;image&quot;] %&gt;.svg&quot; width=&quot;100&quot; alt=&quot;&lt;%= card[&quot;name&quot;] %&gt;&quot;&gt;
  &lt;% end %&gt;
&lt;/p&gt;
&lt;ul&gt;
  &lt;% for card in hand %&gt;
    &lt;li&gt;&lt;%= card[&quot;name&quot;] %&gt;&lt;/li&gt;
  &lt;% end %&gt;
&lt;/ul&gt;
&lt;a href=&quot;/cards&quot;&gt;Deal again&lt;/a&gt;
</code></pre>
<p>That&apos;s a lot of Ruby for a view. It works, and for one page it&apos;s fine. Next week, Ruby that works something out moves into the controller, and the view goes back to mostly showing it.</p>
<h2 id="when-it-went-wrong">When it went wrong</h2>
<ul>
<li><strong>Broken images.</strong> The file name: lowercase, underscores, <code>.svg</code> on the end. View the page source and compare one <code>src</code> with a file name in <code>public/images/cards/</code>.</li>
<li><strong>The same card twice in a hand.</strong> Five <code>rand</code> picks instead of one shuffled deck.</li>
<li><strong>52 cards on the page.</strong> The loop over <code>deck</code> instead of <code>hand</code>.</li>
<li><strong>&quot;Queen of Hearts&quot; in the image name.</strong> The name used where the image belongs: spaces and capitals, so no file matches.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Apps - Contacts - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From the Practice section of the <code>web-apps</code> README:</p>
<blockquote>
<p><strong>Contacts.</strong> Make a page at <code>/contacts</code> that lists every contact&apos;s first and last name and the name of their company. Look the company up with <code>Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })</code>. Then</p></blockquote>]]></description><link>https://entr451.com/web-apps-contacts-solution/</link><guid isPermaLink="false">6ae117ebd9019e19441c3513</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 27 Oct 2026 21:32:01 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From the Practice section of the <code>web-apps</code> README:</p>
<blockquote>
<p><strong>Contacts.</strong> Make a page at <code>/contacts</code> that lists every contact&apos;s first and last name and the name of their company. Look the company up with <code>Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })</code>. Then add each contact&apos;s email.</p>
</blockquote>
<p>It&apos;s the companies page from class, with one new thing: a contact doesn&apos;t hold its company&apos;s name, only its company&apos;s <code>id</code>.</p>
<h2 id="1-the-route-and-the-controller">1. The route and the controller</h2>
<p>In <code>config/routes.rb</code>, under the companies line:</p>
<pre><code class="language-ruby">get(&quot;/contacts&quot;, { :controller =&gt; &quot;contacts&quot;, :action =&gt; &quot;index&quot; })
</code></pre>
<p>In the terminal that isn&apos;t running the server:</p>
<pre><code>bin/rails generate controller contacts
</code></pre>
<p>And the action, in <code>app/controllers/contacts_controller.rb</code>:</p>
<pre><code class="language-ruby">class ContactsController &lt; ApplicationController
  def index
    render({ :template =&gt; &quot;contacts/index&quot; })
  end
end
</code></pre>
<p>The errors on the way are the tacos ones: no controller, unknown action, missing template.</p>
<h2 id="2-every-contacts-name">2. Every contact&apos;s name</h2>
<p><code>app/views/contacts/index.html.erb</code>. The same loop as the companies page, over <code>Contact.all</code>:</p>
<pre><code class="language-erb">&lt;h1&gt;Contacts&lt;/h1&gt;
&lt;ul&gt;
  &lt;% for contact in Contact.all %&gt;
    &lt;li&gt;&lt;%= contact[&quot;first_name&quot;] %&gt; &lt;%= contact[&quot;last_name&quot;] %&gt;&lt;/li&gt;
  &lt;% end %&gt;
&lt;/ul&gt;
</code></pre>
<p>Four names: Tim Cook, Craig Federighi, Andy Jassy, Dario Amodei.</p>
<h2 id="3-the-company-by-its-id">3. The company, by its id</h2>
<p>Open <code>/data</code> and look at the contacts table. Tim Cook&apos;s <code>company_id</code> is 1, and the company whose <code>id</code> is 1 is Apple. So for each contact, find the company whose <code>id</code> is that contact&apos;s <code>company_id</code>:</p>
<pre><code class="language-erb">&lt;% company = Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] }) %&gt;
</code></pre>
<p><code>find_by</code> gives back one row, the first that matches, so <code>company[&quot;name&quot;]</code> is its name. It goes inside the loop, because each contact has its own company:</p>
<pre><code class="language-erb">&lt;h1&gt;Contacts&lt;/h1&gt;
&lt;ul&gt;
  &lt;% for contact in Contact.all %&gt;
    &lt;% company = Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] }) %&gt;
    &lt;li&gt;
      &lt;%= contact[&quot;first_name&quot;] %&gt; &lt;%= contact[&quot;last_name&quot;] %&gt;, &lt;%= company[&quot;name&quot;] %&gt;, &lt;%= contact[&quot;email&quot;] %&gt;
    &lt;/li&gt;
  &lt;% end %&gt;
&lt;/ul&gt;
</code></pre>
<p>The email was the last step: <code>contact[&quot;email&quot;]</code>, one more cell of the same row.</p>
<pre><code>Tim Cook, Apple, tim@apple.com
Craig Federighi, Apple, craig@apple.com
Andy Jassy, Amazon, andy@amazon.com
Dario Amodei, Anthropic, dario@anthropic.com
</code></pre>
<h2 id="what-the-log-shows">What the log shows</h2>
<p>Refresh the page and look at the bottom of <code>log/development.log</code>. One <code>SELECT</code> for the contacts, then one more for each contact&apos;s company, with a <code>WHERE &quot;companies&quot;.&quot;id&quot; = ?</code> in it: five lines in all. The second Apple one starts with <code>CACHE</code>: Craig&apos;s company is Tim&apos;s, and Rails remembered the answer from a moment before, inside the same request. Every other <code>find_by</code> is its own trip to the database. For four contacts that&apos;s nothing. For four thousand it would be slow, and later in the course you&apos;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.</p>
<h2 id="when-it-went-wrong">When it went wrong</h2>
<ul>
<li><strong>undefined method &apos;[]&apos; for nil.</strong> <code>find_by</code> found nothing, so <code>company</code> is <code>nil</code>. Usually a typo in the column: <code>&quot;company_id&quot;</code>, with the underscore. (Or a contact whose company was deleted.)</li>
<li><strong>Every contact shows the same company.</strong> The <code>find_by</code> line is above the loop instead of inside it.</li>
<li><strong>company.name instead of company[&quot;name&quot;].</strong> It works, but stay with the brackets: a row is read the same way everywhere in this course, column name in brackets.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Web Apps - Dice - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From class:</p>
<blockquote>
<ol>
<li>A route for <code>/dice</code> in <code>config/routes.rb</code>.</li>
<li><code>bin/rails generate controller dice</code>, then an <code>index</code> action in it.</li>
<li>An index view in <code>app/views/dice/</code>.</li>
<li>In the view, roll two dice. Show both, and the total.</li>
</ol>
<p>One die: <code>rand(1..6)</code>. Challenge: the pictures, <code>&lt;</code></p></blockquote>]]></description><link>https://entr451.com/web-apps-dice-solution/</link><guid isPermaLink="false">6ae116eed9019e19441c34ec</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 27 Oct 2026 21:29:43 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From class:</p>
<blockquote>
<ol>
<li>A route for <code>/dice</code> in <code>config/routes.rb</code>.</li>
<li><code>bin/rails generate controller dice</code>, then an <code>index</code> action in it.</li>
<li>An index view in <code>app/views/dice/</code>.</li>
<li>In the view, roll two dice. Show both, and the total.</li>
</ol>
<p>One die: <code>rand(1..6)</code>. Challenge: the pictures, <code>&lt;img src=&quot;/images/dice/1.svg&quot;&gt;</code>. <code>1.svg</code> to <code>6.svg</code> are already in <code>public/images/dice/</code>.</p>
<p>Done? Check your page against your three sentences.</p>
</blockquote>
<p>Before you built it, you wrote three sentences that would tell you the page works. Keep them next to you: the last section checks the page against them.</p>
<p>This is the tacos page again, with Ruby doing more of the work. Same three files, same errors, in the same order.</p>
<h2 id="1-the-route">1. The route</h2>
<p>In <code>config/routes.rb</code>, under the tacos line:</p>
<pre><code class="language-ruby">get(&quot;/dice&quot;, { :controller =&gt; &quot;dice&quot;, :action =&gt; &quot;index&quot; })
</code></pre>
<p>Visit <code>/dice</code>: &quot;uninitialized constant DiceController&quot;. Rails read the route and went looking for a controller called dice.</p>
<h2 id="2-the-controller">2. The controller</h2>
<p>In the terminal that isn&apos;t running the server, with the name from the route:</p>
<pre><code>bin/rails generate controller dice
</code></pre>
<p>It makes <code>app/controllers/dice_controller.rb</code>, an empty class, and the empty folder <code>app/views/dice/</code>. Refresh: &quot;The action &apos;index&apos; could not be found for DiceController&quot;. The route asked for <code>index</code>, and an action is a method. Add it:</p>
<pre><code class="language-ruby">class DiceController &lt; ApplicationController
  def index
    render({ :template =&gt; &quot;dice/index&quot; })
  end
end
</code></pre>
<p>Refresh: &quot;Missing template dice/index&quot;. One file to go.</p>
<h2 id="3-the-view-with-numbers-first">3. The view, with numbers first</h2>
<p>Make <code>app/views/dice/index.html.erb</code>, in the folder the generator made. Roll each die once, at the top, and keep the result in a variable:</p>
<pre><code class="language-erb">&lt;% die1 = rand(1..6) %&gt;
&lt;% die2 = rand(1..6) %&gt;
&lt;h1&gt;Dice&lt;/h1&gt;
&lt;p&gt;
  You rolled &lt;%= die1 %&gt; and &lt;%= die2 %&gt;, for a total of &lt;%= die1 + die2 %&gt;.
&lt;/p&gt;
</code></pre>
<p><code>rand(1..6)</code> is a whole number from 1 to 6, picked at random. The variables matter: if you wrote <code>rand(1..6)</code> straight into the sentence, the total would come from two new rolls, not the two on the page. Roll once, then use the variables everywhere.</p>
<p><code>&lt;% %&gt;</code> runs the Ruby and shows nothing; <code>&lt;%= %&gt;</code> runs it and puts the result on the page.</p>
<p>Refresh a few times. Every refresh is a new request, so the view runs again from the top and rolls again.</p>
<h2 id="4-the-pictures">4. The pictures</h2>
<p>The images are named for their number, <code>1.svg</code> to <code>6.svg</code>. So the number goes inside the file name:</p>
<pre><code class="language-erb">&lt;p&gt;
  &lt;img src=&quot;/images/dice/&lt;%= die1 %&gt;.svg&quot; width=&quot;100&quot;&gt;
  &lt;img src=&quot;/images/dice/&lt;%= die2 %&gt;.svg&quot; width=&quot;100&quot;&gt;
&lt;/p&gt;
</code></pre>
<p>When <code>die1</code> is 4, Rails writes <code>&lt;img src=&quot;/images/dice/4.svg&quot;&gt;</code>, and the browser asks for that picture. The path starts with <code>/images</code>, not <code>/public/images</code>: everything in <code>public/</code> is served from the top of the site.</p>
<h2 id="5-roll-again">5. Roll again</h2>
<p>A link back to the same page asks for it again, which rolls again:</p>
<pre><code class="language-erb">&lt;a href=&quot;/dice&quot;&gt;Roll again&lt;/a&gt;
</code></pre>
<h2 id="the-whole-view">The whole view</h2>
<pre><code class="language-erb">&lt;% die1 = rand(1..6) %&gt;
&lt;% die2 = rand(1..6) %&gt;
&lt;h1&gt;Dice&lt;/h1&gt;
&lt;p&gt;
  &lt;img src=&quot;/images/dice/&lt;%= die1 %&gt;.svg&quot; width=&quot;100&quot;&gt;
  &lt;img src=&quot;/images/dice/&lt;%= die2 %&gt;.svg&quot; width=&quot;100&quot;&gt;
&lt;/p&gt;
&lt;p&gt;
  You rolled &lt;%= die1 %&gt; and &lt;%= die2 %&gt;, for a total of &lt;%= die1 + die2 %&gt;.
&lt;/p&gt;
&lt;a href=&quot;/dice&quot;&gt;Roll again&lt;/a&gt;
</code></pre>
<h2 id="check-it-against-your-sentences">Check it against your sentences</h2>
<p>Three that the room came up with, and how to check each one by looking at the page:</p>
<ul>
<li><strong>Each die shows a number from 1 to 6.</strong> Refresh ten times. You should never see a 0 or a 7.</li>
<li><strong>The total is the sum of the two dice.</strong> Add them in your head, every refresh.</li>
<li><strong>A link rolls again.</strong> Click it; the dice change (most of the time: two rolls can match).</li>
</ul>
<p>If one of yours was &quot;it looks good&quot; or &quot;it&apos;s random&quot;, try rewording it into something you could count or compare. &quot;It&apos;s random&quot; is hard to check by looking once; &quot;the dice aren&apos;t the same every time I refresh&quot; is easy.</p>
<h2 id="when-it-went-wrong">When it went wrong</h2>
<ul>
<li><strong>The same roll every time.</strong> A number typed where <code>rand(1..6)</code> belongs.</li>
<li><strong>A broken image.</strong> The path: <code>/images/dice/3.svg</code>, with the slash at the front and no <code>public/</code>.</li>
<li><strong>The total doesn&apos;t match the dice.</strong> <code>rand(1..6)</code> written into the sentence instead of the variables, so the total rolled its own dice.</li>
<li><strong>The generator did nothing.</strong> It was typed into the terminal running the server, which isn&apos;t taking commands. Use the other one.</li>
</ul>
<p>For a slower walk through the same page, with the tacos page before it, see<br>
<a href="https://entr451.com/two-great-things/">Two Great Things</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Adding a column to a table]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p><a href="https://entr451.com/migrations/">Migrations</a> showed how a table is born: <code>bin/rails generate model</code>, columns inside <code>create_table</code>, then <code>bin/rails db:migrate</code>. This is the next thing that happens to every table: a column it needs that wasn&apos;t there on day one.</p>
</blockquote>
<h2 id="the-problem">The problem</h2>
<p>Your <code>movies</code> table is made and</p>]]></description><link>https://entr451.com/adding-a-column/</link><guid isPermaLink="false">6ad8f72ad9019e19441c34cb</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 17:34:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p><a href="https://entr451.com/migrations/">Migrations</a> showed how a table is born: <code>bin/rails generate model</code>, columns inside <code>create_table</code>, then <code>bin/rails db:migrate</code>. This is the next thing that happens to every table: a column it needs that wasn&apos;t there on day one.</p>
</blockquote>
<h2 id="the-problem">The problem</h2>
<p>Your <code>movies</code> table is made and migrated, and your tests are green. Now a new story says every movie has a director, and there is no column for it.</p>
<p>The migration that created the table has already run, and Rails won&apos;t run it again. Editing it changes nothing in the database, and it leaves a file that lies about what happened. So the rule from <a href="https://entr451.com/migrations/">Migrations</a> still holds: never edit a migration you have run.</p>
<p>The fix is not to start over. It is one more migration, one that changes the table instead of creating it.</p>
<h2 id="the-new-migration">The new migration</h2>
<p>Ask Rails for an empty migration. The name says what it does; Rails only needs it to be unique.</p>
<pre><code>bin/rails generate migration AddDirectorToMovies
</code></pre>
<p>That makes a new file in <code>db/migrate/</code>, with a newer timestamp than the one that created the table, so it runs after it. Open it. Its <code>change</code> method is empty. Put one line in it:</p>
<pre><code class="language-ruby">class AddDirectorToMovies &lt; ActiveRecord::Migration[8.1]
  def change
    add_column :movies, :director, :string
  end
end
</code></pre>
<p><code>add_column</code> takes the table, the new column and its type: the same types as before, <code>:string</code> for text and <code>:integer</code> for numbers and ids. Then run it:</p>
<pre><code>bin/rails db:migrate
</code></pre>
<p>Open <code>db/schema.rb</code>. The <code>movies</code> table now lists <code>director</code>. You didn&apos;t write that line; Rails did, from the migration. The rows you already had are still there, each with an empty director until something fills it in.</p>
<h2 id="what-it-does-to-your-tests">What it does to your tests</h2>
<p>Nothing, until a rule says the column matters. The moment your definition of done says &quot;a movie can&apos;t be saved without a director&quot; and the model gets that rule, the tests you already had start to react. Any test that saved a movie on its way to checking something else is now saving one with no director, and what happens next depends on what that test was checking. Read each failure for what the test was relying on, and ask whether any test is now passing for a reason other than its own rule. That is not the tests breaking; that is the tests doing their job. Each of those movies now has to be given a director, the way the real app would. When you ask your agent for the new tests, expect it to touch the old ones too, and read what it changed.</p>
<h2 id="the-other-direction">The other direction</h2>
<p>A column that should go away is the same shape, with <code>remove_column</code>:</p>
<pre><code class="language-ruby">remove_column :movies, :director, :string
</code></pre>
<p>A new migration, never an edit to an old one. The database&apos;s history is the list of files in <code>db/migrate/</code>, in order, and <code>db/schema.rb</code> shows the database&apos;s state up to the most recently executed (i.e. ran) migration.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Related rows, with where]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Week 2 had a <code>WHERE</code>. Week 3 had a foreign key: a column in one table that holds the <code>id</code> of a row in another. Code-along 5 is those two ideas in Ruby, and nothing more. A contact works at one company; the <code>contacts</code> table holds that company&apos;s</p>]]></description><link>https://entr451.com/related-rows-with-where/</link><guid isPermaLink="false">6ad8f031d9019e19441c34b2</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 17:04:21 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Week 2 had a <code>WHERE</code>. Week 3 had a foreign key: a column in one table that holds the <code>id</code> of a row in another. Code-along 5 is those two ideas in Ruby, and nothing more. A contact works at one company; the <code>contacts</code> table holds that company&apos;s <code>id</code>; and you find a contact&apos;s company, or a company&apos;s contacts, by looking the id up.</p>
<h2 id="two-tables-one-column-between-them">Two tables, one column between them</h2>
<p>The rules under <code>Contact</code> in <code>DONE.md</code>:</p>
<pre><code>## Contact
A contact can&apos;t be saved without a company.
I can find all the contacts at a company: Apple has three.
</code></pre>
<p>The prompt asked the agent for the model and its migration as well as the tests, so the migration came first:</p>
<pre><code class="language-ruby">class CreateContacts &lt; ActiveRecord::Migration[8.1]
  def change
    create_table :contacts do |t|
      t.string &quot;first_name&quot;
      t.string &quot;last_name&quot;
      t.string &quot;email&quot;
      t.integer &quot;company_id&quot;

      t.timestamps
    end
  end
end
</code></pre>
<p><code>company_id</code> is an integer because it holds another table&apos;s <code>id</code>. On the Data page, every contact&apos;s <code>company_id</code> is the <code>id</code> of a row in <code>companies</code>. That column is the whole relationship. There&apos;s no other link anywhere.</p>
<h2 id="the-tests">The tests</h2>
<pre><code class="language-ruby">require &quot;test_helper&quot;

class ContactTest &lt; ActiveSupport::TestCase
  # One test for each sentence under Contact in DONE.md, named with that sentence.

  test &quot;a contact can&apos;t be saved without a company&quot; do
    contact = Contact.new
    contact[&quot;first_name&quot;] = &quot;Tim&quot;
    contact[&quot;last_name&quot;] = &quot;Cook&quot;
    contact.save

    assert_equal 0, Contact.all.count
  end

  test &quot;I can find all the contacts at a company: Apple has three&quot; do
    apple = Company.new
    apple[&quot;name&quot;] = &quot;Apple&quot;
    apple.save

    amazon = Company.new
    amazon[&quot;name&quot;] = &quot;Amazon&quot;
    amazon.save

    for first_name in [&quot;Tim&quot;, &quot;Craig&quot;, &quot;Lisa&quot;]
      contact = Contact.new
      contact[&quot;first_name&quot;] = first_name
      contact[&quot;company_id&quot;] = apple[&quot;id&quot;]
      contact.save
    end

    contact = Contact.new
    contact[&quot;first_name&quot;] = &quot;Andy&quot;
    contact[&quot;company_id&quot;] = amazon[&quot;id&quot;]
    contact.save

    apple_contacts = Contact.where({ &quot;company_id&quot; =&gt; apple[&quot;id&quot;] })
    assert_equal 3, apple_contacts.count
  end

end
</code></pre>
<p>On the first run, one fails and one passes. &quot;Can&apos;t be saved without a company&quot; fails because nothing enforces it yet. &quot;Apple has three&quot; passes on the empty model, because finding rows by a column comes with the table; <code>where</code> is not something you add to a model. Its test is still worth having: it says what &quot;at a company&quot; means (the <code>company_id</code> matches) and that Andy, at Amazon, is not counted.</p>
<p>One line makes the first pass:</p>
<pre><code class="language-ruby">class Contact &lt; ApplicationRecord
  validates :company_id, presence: true
end
</code></pre>
<h2 id="from-a-company-to-its-contacts">From a company to its contacts</h2>
<pre><code class="language-ruby">Contact.where({ &quot;company_id&quot; =&gt; apple[&quot;id&quot;] })
</code></pre>
<p>A list of every contact whose <code>company_id</code> is Apple&apos;s <code>id</code>. It&apos;s the <code>WHERE</code> from week 2, written in Ruby, and the SQL it writes is in <code>log/development.log</code>:</p>
<pre><code class="language-sql">SELECT &quot;contacts&quot;.* FROM &quot;contacts&quot; WHERE &quot;contacts&quot;.&quot;company_id&quot; = 1
</code></pre>
<p><code>where</code> always returns a list, even when it finds one row, or none.</p>
<h2 id="from-a-contact-to-its-company">From a contact to its company</h2>
<pre><code class="language-ruby">Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })
</code></pre>
<p>One row, the company whose <code>id</code> the contact holds. <code>find_by</code> returns a single row, or <code>nil</code> if there isn&apos;t one.</p>
<p>That&apos;s the script from class, <code>scripts/list-contacts.rb</code>, after it saves Apple, Amazon and four contacts:</p>
<pre><code class="language-ruby"># Each contact holds its company&apos;s id. Look the company up by that id.
for contact in Contact.all
  company = Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })
  puts &quot;#{contact[&quot;first_name&quot;]} #{contact[&quot;last_name&quot;]} (#{company[&quot;name&quot;]})&quot;
end
</code></pre>
<pre><code>Tim Cook (Apple)
Craig Federighi (Apple)
Lisa Jackson (Apple)
Andy Jassy (Amazon)
</code></pre>
<p>Every line is one lookup. When the thing you want is two tables away (an activity&apos;s contact&apos;s company, in lab 3), it&apos;s two lookups, following the ids one at a time.</p>
<h2 id="why-not-contactcompany">Why not <code>contact.company</code>?</h2>
<p>Rails can hide this lookup. With <code>belongs_to :company</code> in the model, <code>contact.company</code> does the <code>find_by</code> for you, and <code>has_many :contacts</code> gives you <code>apple.contacts</code>. It&apos;s shorter, and in a bigger app you&apos;d use it.</p>
<p>This repo&apos;s <code>AGENTS.md</code> tells the agent not to, and the reason is the same as for tests first: so you can see what&apos;s happening. <code>contact.company</code> looks like the contact contains a company. It doesn&apos;t. It holds a number, and somebody runs a <code>WHERE</code> with it. Once that&apos;s obvious to you, the shorthand is a convenience. Before it is, it&apos;s a mystery, and the first time it returns <code>nil</code> you won&apos;t know where to look.</p>
<p>If your agent wrote <code>has_many</code> or <code>belongs_to</code>, or a <code>contact.company</code>, point it at <code>AGENTS.md</code> and ask for the <code>where</code>.</p>
<h2 id="the-rule-of-thumb">The rule of thumb</h2>
<p>Which table holds the id? The many side. One company has many contacts, so each contact holds a <code>company_id</code>; the company holds nothing about its contacts. One salesperson logs many activities, so each activity holds a <code>salesperson_id</code>. To go from the one to the many, <code>where</code> on that column. To go from the many to the one, <code>find_by</code> on <code>id</code>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[The definition of done, and tests]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Code-along 3 ended with a <code>Company</code> model that could save, find and change rows without a line of code in <code>app/models/company.rb</code>. The migration gave it all of that. What it couldn&apos;t do was refuse anything: a company with no name saved as happily as Apple.</p>]]></description><link>https://entr451.com/definition-of-done-and-tests/</link><guid isPermaLink="false">6ad8ef6dd9019e19441c348a</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 17:01:45 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Code-along 3 ended with a <code>Company</code> model that could save, find and change rows without a line of code in <code>app/models/company.rb</code>. The migration gave it all of that. What it couldn&apos;t do was refuse anything: a company with no name saved as happily as Apple. This post is the second half of that code-along and the whole of code-along 4: how a rule gets written down, how it becomes a test, and what the test is for.</p>
<h2 id="the-rule-comes-first-in-english">The rule comes first, in English</h2>
<p><code>DONE.md</code> is a file of sentences. Under each model&apos;s heading, one line per thing that must be true:</p>
<pre><code>## Company
A company can&apos;t be saved without a name.
Two companies can&apos;t have the same name.
</code></pre>
<p>No Ruby. The question it answers is &quot;what can&apos;t be allowed?&quot;, and the person answering it is you, because you&apos;re the one who knows what the business needs. The agent reads this file and never writes to it. That split is deliberate: if the agent wrote the rules, it would be grading its own work.</p>
<p>This is the definition of done. Not &quot;the code is written&quot;, but &quot;these sentences are true, and something proves it.&quot;</p>
<h2 id="one-test-per-sentence">One test per sentence</h2>
<p>The prompt from class:</p>
<pre><code>My rules for Company are under Company in DONE.md. Write a test for each one, run them and show me they fail. Don&apos;t change the model yet.
</code></pre>
<p>The agent writes <code>test/models/company_test.rb</code>:</p>
<pre><code class="language-ruby">require &quot;test_helper&quot;

class CompanyTest &lt; ActiveSupport::TestCase
  # One test for each sentence under Company in DONE.md, named with that sentence.

  test &quot;a company can&apos;t be saved without a name&quot; do
    company = Company.new
    company[&quot;city&quot;] = &quot;Beaverton&quot;
    company[&quot;state&quot;] = &quot;OR&quot;
    company.save

    assert_equal 0, Company.all.count
  end

  test &quot;two companies can&apos;t have the same name&quot; do
    company1 = Company.new
    company1[&quot;name&quot;] = &quot;Nike&quot;
    company1.save

    company2 = Company.new
    company2[&quot;name&quot;] = &quot;Nike&quot;
    company2.save

    assert_equal 1, Company.all.count
  end

end
</code></pre>
<p>Each <code>test</code> block is one experiment. It sets up a situation (a company with no name), does the thing (<code>save</code>), and states what must be true afterwards (<code>assert_equal 0, Company.all.count</code>: no rows). The name of the test is the sentence from <code>DONE.md</code>, word for word, so that when a test fails, its name tells you which rule broke.</p>
<p>The tests run against a separate, empty database, rebuilt for every test. Nothing you do in <code>scripts/</code> or on the Data page is visible to them, and nothing they do touches your data.</p>
<h2 id="red-first">Red first</h2>
<pre><code>bin/rails test
</code></pre>
<pre><code>Failure:
CompanyTest#test_a_company_can&apos;t_be_saved_without_a_name
Expected: 0
  Actual: 1

Failure:
CompanyTest#test_two_companies_can&apos;t_have_the_same_name
Expected: 1
  Actual: 2

2 runs, 2 assertions, 2 failures, 0 errors, 0 skips
</code></pre>
<p>Both fail, and that is the result you wanted. The model has no rules yet, so a nameless company saved (1 row, not 0) and both Nikes saved (2, not 1). The tests are describing a problem that really exists.</p>
<p>This is why the prompt said &quot;don&apos;t change the model yet.&quot; A test you have never seen fail might not be checking anything. If the agent had written the model and the tests together and shown you green, you&apos;d have no way to tell a test that proves the rule from a test that passes no matter what. Code-along 0 made the same point with one <code>=</code> in place of <code>==</code>: a check that can&apos;t fail is worse than no check, because it looks like one.</p>
<p>The name for this way of working is test-driven development, TDD: write the test, watch it fail, write the code, watch it pass. The order is the whole technique.</p>
<h2 id="check-before-green">Check before green</h2>
<p>Before &quot;make them pass&quot;, open <code>DONE.md</code> beside the test file and do two things the agent can&apos;t do for you:</p>
<ul>
<li><strong>Count.</strong> Every sentence has a test? The agent will say &quot;done&quot; either way. A sentence with no test is a rule your app doesn&apos;t enforce, and you&apos;d only find out when it&apos;s broken.</li>
<li><strong>Read.</strong> Each test checks what its sentence says? The first test above sets a city and a state and leaves out the name, then expects nothing saved. If it had set a name too, it would be testing nothing.</li>
</ul>
<p>This is the manager&apos;s seat. You don&apos;t need to write the test to tell whether it tests the rule.</p>
<h2 id="green">Green</h2>
<pre><code>Make them pass.
</code></pre>
<p>The agent adds one line to <code>app/models/company.rb</code>:</p>
<pre><code class="language-ruby">class Company &lt; ApplicationRecord
  validates :name, presence: true, uniqueness: true
end
</code></pre>
<p><code>validates</code> states a rule on a column. <code>presence: true</code> refuses a blank; <code>uniqueness: true</code> looks in the table and refuses a duplicate. When a rule fails, <code>save</code> returns <code>false</code> instead of saving, and <code>company.errors.full_messages</code> says which rule: <code>Name can&apos;t be blank</code>.</p>
<pre><code>2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
</code></pre>
<p>Run that yourself, not just the agent. Then read the model. One line for two sentences, and you can read it against the sentences: presence is the first rule, uniqueness the second.</p>
<h2 id="what-the-tests-are-for-the-helpful-change">What the tests are for: the helpful change</h2>
<p>Code-along 4. Imports from a spreadsheet keep failing on rows with no company name. A developer decides to help: when the name is blank, fill one in.</p>
<p><code>code-along/4-introduce-bug.rb</code> tries to save a company with a city and no name. Before any change:</p>
<pre><code>Not saved: Name can&apos;t be blank
</code></pre>
<p>Then the change, pasted into the model:</p>
<pre><code class="language-ruby">before_validation :fill_in_missing_name

def fill_in_missing_name
  if self[&quot;name&quot;].blank?
    self[&quot;name&quot;] = &quot;Unknown company&quot;
  end
end
</code></pre>
<p><code>before_validation</code> runs the method every time a company is about to be checked and saved, so the blank is filled before <code>validates</code> looks. Run the file again:</p>
<pre><code>Saved, with the name &quot;Unknown company&quot;
</code></pre>
<p>The import works now. The app runs. Nobody would notice, except:</p>
<pre><code>bin/rails test
</code></pre>
<pre><code>Failure:
CompanyTest#test_a_company_can&apos;t_be_saved_without_a_name
Expected: 0
  Actual: 1

2 runs, 2 assertions, 1 failures, 0 errors, 0 skips
</code></pre>
<p>The test named with the rule is the one that failed. It says, in the words you wrote, what the helpful change broke. The developer meant well, the code is tidy, and the rule is gone.<br>
Without the test, the first &quot;Unknown company&quot; would turn up in a report three weeks later.</p>
<p>Take the lines back out, run the tests, green again.</p>
<h2 id="why-this-is-the-weeks-idea">Why this is the week&apos;s idea</h2>
<p>The agent in your terminal is non-deterministic. Ask it the same thing twice and you get two different files. That is fine as long as something deterministic decides whether what it wrote is right, and that something is the tests. You write the rule; the test enforces it; the agent works until the test is green; and the next change, by you, by a teammate or by an agent, runs into the same test.</p>
<p>Write the definition of done before the code. Everything else in the week follows from it.</p>
<h2 id="if-no-test-fails-in-step-5">If no test fails in step 5</h2>
<p>Then the rule was never written down as a test. Add the sentence to <code>DONE.md</code> and have your agent write its test. Lab 4 is a harder version of exactly this: a change that gets past a test which was written, and why.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Models and Tests - Lab 4 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/4-regression.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>Someone logging activities in a hurry keeps leaving the salesperson blank, and the save fails. A developer decides to help: when the salesperson is missing, use the first salesperson in the table.</p>
</blockquote>
<p>Code-along 4 did this to <code>Company</code> with</p>]]></description><link>https://entr451.com/models-and-tests-lab-4-solution/</link><guid isPermaLink="false">6ad8eea5d9019e19441c346b</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 16:57:38 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/4-regression.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>Someone logging activities in a hurry keeps leaving the salesperson blank, and the save fails. A developer decides to help: when the salesperson is missing, use the first salesperson in the table.</p>
</blockquote>
<p>Code-along 4 did this to <code>Company</code> with a filled-in name, and a test caught it. This lab does it to <code>Activity</code>, and the question is whether yours will.</p>
<h2 id="1-and-2-before-you-paste-anything">1 and 2. Before you paste anything</h2>
<pre><code>bin/rails test
</code></pre>
<p>Everything passes. Now the prediction, on paper: is this a bug?</p>
<p>Yes. The rule in <code>DONE.md</code> is &quot;An activity can&apos;t be saved without a salesperson&quot;, and the change saves exactly such an activity by inventing a salesperson for it. A note logged by nobody now belongs to whoever happens to be first in the table, and that person gets the credit, or the blame. So you expect the first test under Activity to fail.</p>
<p>Write that down before step 3. The lab is about comparing the prediction with what happens.</p>
<h2 id="3-and-4-the-helpful-change">3 and 4. The helpful change</h2>
<p>Pasted into <code>app/models/activity.rb</code>, under the class line:</p>
<pre><code class="language-ruby">class Activity &lt; ApplicationRecord
  before_validation :fill_in_missing_salesperson

  def fill_in_missing_salesperson
    if self[&quot;salesperson_id&quot;].blank?
      first_salesperson = Salesperson.all[0]
      if first_salesperson != nil
        self[&quot;salesperson_id&quot;] = first_salesperson[&quot;id&quot;]
      end
    end
  end

  validates :salesperson_id, presence: true
  validates :contact_id, presence: true
end
</code></pre>
<p><code>before_validation</code> runs the method every time an activity is about to be checked and saved, so by the time <code>validates :salesperson_id, presence: true</code> looks, the blank has been filled. The rule is still in the file, and it&apos;s now unreachable.</p>
<pre><code>bin/rails test
</code></pre>
<pre><code>9 runs, 9 assertions, 0 failures, 0 errors, 0 skips
</code></pre>
<p>Nothing failed. The prediction was wrong, and the bug is in. To prove it&apos;s in, a script that does what the hurried person did, run with Ben and Brian in the table from lab 2:</p>
<pre><code class="language-ruby">activity = Activity.new
activity[&quot;contact_id&quot;] = 1
activity[&quot;note&quot;] = &quot;left a voicemail&quot;

if activity.save
  puts &quot;Saved, with salesperson_id #{activity[&quot;salesperson_id&quot;]}&quot;
  activity.destroy
else
  puts &quot;Not saved: #{activity.errors.full_messages.join(&quot;, &quot;)}&quot;
end
</code></pre>
<pre><code>Saved, with salesperson_id 1
</code></pre>
<p>The voicemail is Ben&apos;s now (whatever his id is in your table).</p>
<h2 id="5-why-the-test-didnt-catch-it">5. Why the test didn&apos;t catch it</h2>
<p>Read the test again:</p>
<pre><code class="language-ruby">test &quot;an activity can&apos;t be saved without a salesperson&quot; do
  activity = Activity.new
  activity[&quot;contact_id&quot;] = 1
  activity[&quot;note&quot;] = &quot;met at Cupertino&quot;
  activity.save

  assert_equal 0, Activity.all.count
end
</code></pre>
<p>The test database is empty when each test starts. There are no salespeople in it, so <code>Salesperson.all[0]</code> is <code>nil</code>, the <code>if first_salesperson != nil</code> does nothing, the <code>salesperson_id</code> stays blank, and the validation refuses the save. The test passes for a reason that has nothing to do with your rule: there was nobody to fill in.</p>
<p>That&apos;s the lab&apos;s hint, and it&apos;s a general lesson about tests. A test proves the rule only in the situation it sets up. &quot;Can&apos;t be saved without a salesperson&quot; was proven for an empty table. The change only misbehaves when the table has someone in it, and no test put anyone there.</p>
<p>So the sentence to add under <code>Activity</code> in <code>DONE.md</code> names that situation:</p>
<pre><code>An activity can&apos;t be saved without a salesperson, even when there are salespeople.
</code></pre>
<p>Ask your agent for its test, with the change still pasted in. It should fail. Something like:</p>
<pre><code class="language-ruby">test &quot;an activity can&apos;t be saved without a salesperson, even when there are salespeople&quot; do
  ben = Salesperson.new
  ben[&quot;first_name&quot;] = &quot;Ben&quot;
  ben[&quot;email&quot;] = &quot;ben@example.com&quot;
  ben.save

  activity = Activity.new
  activity[&quot;contact_id&quot;] = 1
  activity[&quot;note&quot;] = &quot;met at Cupertino&quot;
  activity.save

  assert_equal 0, Activity.all.count
end
</code></pre>
<pre><code>Failure:
ActivityTest#test_an_activity_can&apos;t_be_saved_without_a_salesperson,_even_when_there_are_salespeople
Expected: 0
  Actual: 1

4 runs, 4 assertions, 1 failures, 0 errors, 0 skips
</code></pre>
<p>Now it&apos;s caught. If the new test passed with the change still in, it isn&apos;t testing the rule yet: check that it saves a salesperson first.</p>
<h2 id="6-take-the-change-out">6. Take the change out</h2>
<p>Remove the <code>before_validation</code> line and the method, save, and run the tests:</p>
<pre><code>10 runs, 10 assertions, 0 failures, 0 errors, 0 skips
</code></pre>
<p>Keep the new test. It&apos;s the one that would have caught this, and the next helpful change is usually made by an agent, in a hurry, with the best intentions.</p>
<h2 id="reflection">Reflection</h2>
<p>A test suite is only as good as the rules behind it, and a rule is only as good as the situations its test sets up. Your first test said &quot;without a salesperson&quot; and meant &quot;with no salespeople anywhere&quot;, because that&apos;s the only case it built. Nobody wrote the other case down, so nothing protected it. When a change gets through, the fix is rarely in the model first. It&apos;s a sentence in <code>DONE.md</code>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Models and Tests - Lab 3 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/3-activities.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>An activity is one thing a salesperson did with one contact: &quot;met at Cupertino&quot;. It holds two ids, the salesperson&apos;s and the contact&apos;s.</p>
</blockquote>
<p>This is the lab about the ids. An activity doesn&</p>]]></description><link>https://entr451.com/models-and-tests-lab-3-solution/</link><guid isPermaLink="false">6ad8ee16d9019e19441c344e</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 16:55:29 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/3-activities.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>An activity is one thing a salesperson did with one contact: &quot;met at Cupertino&quot;. It holds two ids, the salesperson&apos;s and the contact&apos;s.</p>
</blockquote>
<p>This is the lab about the ids. An activity doesn&apos;t contain a salesperson or a contact; it holds their <code>id</code>s, and every question you ask of it (&quot;whose activity is this?&quot;, &quot;what did Ben do with Tim?&quot;) is answered by looking rows up by those ids with <code>where</code> or <code>find_by</code>. There is no <code>has_many</code> or <code>belongs_to</code> in this repo, on purpose: the rules in <code>AGENTS.md</code> ask your agent for the lookup, so you can see it.</p>
<h2 id="1-the-rules">1. The rules</h2>
<p>Under <code>Activity</code> in <code>DONE.md</code>:</p>
<pre><code>## Activity
An activity can&apos;t be saved without a salesperson.
An activity can&apos;t be saved without a contact.
I can find one salesperson&apos;s activities with one contact.
</code></pre>
<p>The third is a different kind of rule from the first two. It doesn&apos;t forbid anything; it says what you must be able to find. Both kinds belong in <code>DONE.md</code>.</p>
<h2 id="2-tests-first-red">2. Tests first, red</h2>
<p>Your prompt, in your own words, along the lines of lab 2&apos;s: a test for each sentence, run them, show them failing, don&apos;t change the model yet. The agent should produce something like:</p>
<pre><code class="language-ruby">require &quot;test_helper&quot;

class ActivityTest &lt; ActiveSupport::TestCase
  # One test for each sentence under Activity in DONE.md, named with that sentence.

  test &quot;an activity can&apos;t be saved without a salesperson&quot; do
    activity = Activity.new
    activity[&quot;contact_id&quot;] = 1
    activity[&quot;note&quot;] = &quot;met at Cupertino&quot;
    activity.save

    assert_equal 0, Activity.all.count
  end

  test &quot;an activity can&apos;t be saved without a contact&quot; do
    activity = Activity.new
    activity[&quot;salesperson_id&quot;] = 1
    activity[&quot;note&quot;] = &quot;met at Cupertino&quot;
    activity.save

    assert_equal 0, Activity.all.count
  end

  test &quot;I can find one salesperson&apos;s activities with one contact&quot; do
    for note in [&quot;quick checkin over facetime&quot;, &quot;met at Cupertino&quot;]
      activity = Activity.new
      activity[&quot;salesperson_id&quot;] = 1
      activity[&quot;contact_id&quot;] = 1
      activity[&quot;note&quot;] = note
      activity.save
    end

    activity = Activity.new
    activity[&quot;salesperson_id&quot;] = 1
    activity[&quot;contact_id&quot;] = 2
    activity[&quot;note&quot;] = &quot;met at Amazon HQ&quot;
    activity.save

    found = Activity.where({ &quot;salesperson_id&quot; =&gt; 1, &quot;contact_id&quot; =&gt; 1 })
    assert_equal 2, found.count
  end

end
</code></pre>
<pre><code>3 runs, 3 assertions, 2 failures, 0 errors, 0 skips
</code></pre>
<p>Two fail, one passes, and that is the right first run. The two &quot;can&apos;t be saved&quot; rules fail because nothing enforces them yet. The &quot;I can find&quot; rule passes on the empty model, because<br>
finding rows by a column comes free with the table: <code>where</code> needs no code of yours. A rule that passes before you&apos;ve written anything is worth a second look (is it testing something?), and here the answer is yes: it checks that two of the three activities come back, not three.</p>
<p>Notice the ids in the tests are made up: <code>1</code>, <code>2</code>. The test database starts empty for every test, so there&apos;s no salesperson 1 to look up; the test only needs an activity to <em>hold</em> an id. That is both convenient and a trap, and lab 4 springs it.</p>
<h2 id="3-check-then-green">3. Check, then green</h2>
<p><code>DONE.md</code> beside <code>test/models/activity_test.rb</code>: three sentences, three tests, each named with its sentence, each checking what it says. Then &quot;Make them pass&quot;:</p>
<pre><code class="language-ruby">class Activity &lt; ApplicationRecord
  validates :salesperson_id, presence: true
  validates :contact_id, presence: true
end
</code></pre>
<pre><code>bin/rails test
</code></pre>
<p>Every test in the repo green, Company and Contact included.</p>
<h2 id="4-the-script">4. The script</h2>
<p><code>scripts/activities.rb</code> needs rows to point at, so run <code>scripts/list-contacts.rb</code> (from class) and <code>scripts/salespeople.rb</code> (lab 2) first.</p>
<pre><code class="language-ruby"># Start empty, so this can run more than once
Activity.destroy_all

ben = Salesperson.find_by({ &quot;first_name&quot; =&gt; &quot;Ben&quot;, &quot;last_name&quot; =&gt; &quot;Block&quot; })
tim = Contact.find_by({ &quot;first_name&quot; =&gt; &quot;Tim&quot;, &quot;last_name&quot; =&gt; &quot;Cook&quot; })
andy = Contact.find_by({ &quot;first_name&quot; =&gt; &quot;Andy&quot;, &quot;last_name&quot; =&gt; &quot;Jassy&quot; })

activity = Activity.new
activity[&quot;salesperson_id&quot;] = ben[&quot;id&quot;]
activity[&quot;contact_id&quot;] = tim[&quot;id&quot;]
activity[&quot;note&quot;] = &quot;quick checkin over facetime&quot;
activity.save

activity = Activity.new
activity[&quot;salesperson_id&quot;] = ben[&quot;id&quot;]
activity[&quot;contact_id&quot;] = tim[&quot;id&quot;]
activity[&quot;note&quot;] = &quot;met at Cupertino&quot;
activity.save

activity = Activity.new
activity[&quot;salesperson_id&quot;] = ben[&quot;id&quot;]
activity[&quot;contact_id&quot;] = andy[&quot;id&quot;]
activity[&quot;note&quot;] = &quot;met at Amazon HQ&quot;
activity.save

puts &quot;Activities between Ben and Tim Cook:&quot;
for activity in Activity.where({ &quot;salesperson_id&quot; =&gt; ben[&quot;id&quot;], &quot;contact_id&quot; =&gt; tim[&quot;id&quot;] })
  puts &quot;- #{activity[&quot;note&quot;]}&quot;
end
</code></pre>
<pre><code>Activities between Ben and Tim Cook:
- quick checkin over facetime
- met at Cupertino
</code></pre>
<p>Read how it found them. The script never stores &quot;Ben&quot; in an activity. It looks Ben up once, takes his <code>id</code>, and puts that number in each row. The question &quot;what did Ben do with Tim?&quot; is then one <code>where</code> with two ids. That is the same <code>WHERE salesperson_id = 1 AND contact_id = 1</code> you&apos;d write in SQL, and it&apos;s in <code>log/development.log</code> if you want to see it.</p>
<p>If your agent wrote <code>activity.salesperson</code> or <code>ben.activities</code>, it reached for an association. Point it at <code>AGENTS.md</code>.</p>
<h2 id="challenge-4-and-4a-all-of-bens-activities-with-the-company">Challenge 4 and 4a: all of Ben&apos;s activities, with the company</h2>
<pre><code class="language-ruby">puts &quot;Ben&apos;s Activities:&quot;
for activity in Activity.where({ &quot;salesperson_id&quot; =&gt; ben[&quot;id&quot;] })
  contact = Contact.find_by({ &quot;id&quot; =&gt; activity[&quot;contact_id&quot;] })
  company = Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })
  puts &quot;#{contact[&quot;first_name&quot;]} #{contact[&quot;last_name&quot;]} (#{company[&quot;name&quot;]}) - #{activity[&quot;note&quot;]}&quot;
end
</code></pre>
<pre><code>Ben&apos;s Activities:
Tim Cook (Apple) - quick checkin over facetime
Tim Cook (Apple) - met at Cupertino
Andy Jassy (Amazon) - met at Amazon HQ
</code></pre>
<p>Two lookups per line, following the ids: the activity holds a <code>contact_id</code>, so find the contact; the contact holds a <code>company_id</code>, so find the company. Each <code>find_by</code> on an <code>id</code> is one row, so the result is a single contact and a single company, not a list.</p>
<h2 id="challenge-5-a-count-per-salesperson">Challenge 5: a count per salesperson</h2>
<pre><code class="language-ruby">for salesperson in Salesperson.all
  count = Activity.where({ &quot;salesperson_id&quot; =&gt; salesperson[&quot;id&quot;] }).count
  puts &quot;#{salesperson[&quot;first_name&quot;]} #{salesperson[&quot;last_name&quot;]}: #{count} activities&quot;
end
</code></pre>
<pre><code>Ben Block: 3 activities
Brian Eng: 0 activities
</code></pre>
<p>Brian&apos;s zero is the line to check. <code>where</code> with an id nobody used returns an empty list, and an empty list counts as 0, so a salesperson with no activities still gets a line. If your<br>
version skipped Brian, it probably looped over activities instead of salespeople.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Models and Tests - Lab 2 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/2-salespeople.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>You write the rules in plain English; your agent writes the Ruby. Needs lab 1 first.</p>
</blockquote>
<p>Four steps: the rules in <code>DONE.md</code>, a prompt that gets tests for them and nothing else, your check of those tests, then</p>]]></description><link>https://entr451.com/models-and-tests-lab-2-solution/</link><guid isPermaLink="false">6ad8ed6cd9019e19441c3431</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 16:53:21 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/2-salespeople.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>You write the rules in plain English; your agent writes the Ruby. Needs lab 1 first.</p>
</blockquote>
<p>Four steps: the rules in <code>DONE.md</code>, a prompt that gets tests for them and nothing else, your check of those tests, then the code and a script. Your agent&apos;s Ruby won&apos;t match what&apos;s below line for line, and it shouldn&apos;t. What should match is the shape: every sentence has a test, each test checks its sentence, and the tests failed before the model changed.</p>
<h2 id="1-the-rules">1. The rules</h2>
<p>Under <code>Salesperson</code> in <code>DONE.md</code>, one sentence per line. The lab suggests two questions, and each becomes a rule:</p>
<pre><code>## Salesperson
A salesperson can&apos;t be saved without a first name.
Two salespeople can&apos;t have the same email.
</code></pre>
<p>You could write more. &quot;A salesperson can&apos;t be saved without an email&quot; is a reasonable third. Every sentence you write is a rule the app will enforce, so write the ones you mean. A rule you don&apos;t write down isn&apos;t a rule, and lab 4 is about what that costs.</p>
<h2 id="2-the-prompt">2. The prompt</h2>
<p>Code-along 3&apos;s prompt, with one word changed:</p>
<pre><code>My rules for Salesperson are under Salesperson in DONE.md. Write a test for each one, run them and show me they fail. Don&apos;t change the model yet.
</code></pre>
<p>&quot;Don&apos;t change the model yet&quot; is the important half. Without it, an agent will happily write the tests and the code in one go and show you green. Green on the first run tells you nothing: a test you have never seen fail might not be testing anything.</p>
<p>The agent should write something like this in <code>test/models/salesperson_test.rb</code>:</p>
<pre><code class="language-ruby">require &quot;test_helper&quot;

class SalespersonTest &lt; ActiveSupport::TestCase
  # One test for each sentence under Salesperson in DONE.md, named with that sentence.

  test &quot;a salesperson can&apos;t be saved without a first name&quot; do
    salesperson = Salesperson.new
    salesperson[&quot;last_name&quot;] = &quot;Block&quot;
    salesperson[&quot;email&quot;] = &quot;ben@example.com&quot;
    salesperson.save

    assert_equal 0, Salesperson.all.count
  end

  test &quot;two salespeople can&apos;t have the same email&quot; do
    salesperson1 = Salesperson.new
    salesperson1[&quot;first_name&quot;] = &quot;Ben&quot;
    salesperson1[&quot;email&quot;] = &quot;ben@example.com&quot;
    salesperson1.save

    salesperson2 = Salesperson.new
    salesperson2[&quot;first_name&quot;] = &quot;Brian&quot;
    salesperson2[&quot;email&quot;] = &quot;ben@example.com&quot;
    salesperson2.save

    assert_equal 1, Salesperson.all.count
  end

end
</code></pre>
<p>And it should run them and show you this:</p>
<pre><code>Failure:
SalespersonTest#test_a_salesperson_can&apos;t_be_saved_without_a_first_name
Expected: 0
  Actual: 1

Failure:
SalespersonTest#test_two_salespeople_can&apos;t_have_the_same_email
Expected: 1
  Actual: 2

2 runs, 2 assertions, 2 failures, 0 errors, 0 skips
</code></pre>
<p>Read the failures. The first says a salesperson with no first name was saved: one row where there should be none. The second says both Bens were saved. Both rules are broken, which is right, because nothing enforces them yet.</p>
<h2 id="3-check-its-work">3. Check its work</h2>
<p>Before asking for the code, open <code>DONE.md</code> and the test file side by side.</p>
<ul>
<li><strong>Count.</strong> Two sentences, two tests. A sentence with no test is a rule your app will never enforce, however sure the agent sounded when it said &quot;done&quot;.</li>
<li><strong>Match the names.</strong> Each test is named with its sentence, so you can pair them by reading.</li>
<li><strong>Read each test.</strong> Does it do what the sentence says? The first test leaves out the first name and expects nothing saved. The second saves the same email twice and expects one row. A test that set the first name and then expected a save to fail would be checking the wrong thing, and would stay red no matter what the model did.</li>
</ul>
<p>An agent will sometimes test a rule you didn&apos;t write (a presence check on email, say). That isn&apos;t wrong, but it isn&apos;t yours either: either add the sentence to <code>DONE.md</code> or ask for the test to go.</p>
<p>Then:</p>
<pre><code>Make them pass.
</code></pre>
<h2 id="4-the-model">4. The model</h2>
<p>One line per rule:</p>
<pre><code class="language-ruby">class Salesperson &lt; ApplicationRecord
  validates :first_name, presence: true
  validates :email, uniqueness: true
end
</code></pre>
<p><code>validates</code> is how a model states a rule. <code>presence: true</code> refuses to save a row with that column blank. <code>uniqueness: true</code> looks in the table before saving and refuses if the value is already there. When a rule fails, <code>save</code> returns <code>false</code> instead of saving, and <code>errors.full_messages</code> says why: <code>Email has already been taken</code>.</p>
<p>Run them yourself, not just the agent:</p>
<pre><code>bin/rails test
</code></pre>
<pre><code>2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
</code></pre>
<p>(If you run the whole suite, the Company and Contact tests from class run too.)</p>
<h2 id="5-the-script">5. The script</h2>
<p>Ask for <code>scripts/salespeople.rb</code>: delete every salesperson, create two, print the count and each full name.</p>
<pre><code class="language-ruby"># Start empty, so this can run more than once
Salesperson.destroy_all

ben = Salesperson.new
ben[&quot;first_name&quot;] = &quot;Ben&quot;
ben[&quot;last_name&quot;] = &quot;Block&quot;
ben[&quot;email&quot;] = &quot;ben@example.com&quot;
ben.save

brian = Salesperson.new
brian[&quot;first_name&quot;] = &quot;Brian&quot;
brian[&quot;last_name&quot;] = &quot;Eng&quot;
brian[&quot;email&quot;] = &quot;brian@example.com&quot;
brian.save

puts &quot;Salespeople: #{Salesperson.all.count}&quot;
for salesperson in Salesperson.all
  puts &quot;#{salesperson[&quot;first_name&quot;]} #{salesperson[&quot;last_name&quot;]}&quot;
end
</code></pre>
<pre><code>bin/rails runner scripts/salespeople.rb
</code></pre>
<pre><code>Salespeople: 2
Ben Block
Brian Eng
</code></pre>
<p>The <code>destroy_all</code> at the top is what lets you run it twice. Without it, the second run would try to save a second <code>ben@example.com</code>, your uniqueness rule would refuse it, and the count would stay at 2 while the script quietly skipped a row. Comment it out and run the script twice to see that happen.</p>
<h2 id="challenge-change-an-email-after-its-saved">Challenge: change an email after it&apos;s saved</h2>
<pre><code class="language-ruby">ben[&quot;email&quot;] = &quot;bblock@example.com&quot;
ben.save
</code></pre>
<p>Before you run it: what should the output show? The same three lines. The script never prints an email, so a changed email changes nothing you can see. That&apos;s the point of the question: the data changed, and the output didn&apos;t tell you.</p>
<p>To see the change, print the email, or look at the Data page in the browser, or ask the database:</p>
<pre><code class="language-ruby">puts Salesperson.find_by({ &quot;email&quot; =&gt; &quot;ben@example.com&quot; }).inspect   # nil
puts Salesperson.find_by({ &quot;email&quot; =&gt; &quot;bblock@example.com&quot; })[&quot;first_name&quot;]   # Ben
</code></pre>
<p>The first is <code>nil</code>: nobody has the old email any more. Keep that in mind for lab 3, which looks people up. Look them up by something that doesn&apos;t change, like a name, or by the current email.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Models and Tests - Lab 1 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/1-migrations.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>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). <code>crm-schema.sql</code> has all four tables in SQL; use it</p></blockquote>]]></description><link>https://entr451.com/models-and-tests-lab-1-solution/</link><guid isPermaLink="false">6ad8ece2d9019e19441c341e</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 16:50:22 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/1-migrations.rb</code> in your copy of <code>models-and-tests</code>:</p>
<blockquote>
<p>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). <code>crm-schema.sql</code> has all four tables in SQL; use it as your reference.</p>
<p>Write these two migrations yourself, no agent, the way you wrote the one for companies.</p>
</blockquote>
<p>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.</p>
<h2 id="salesperson">Salesperson</h2>
<p>From <code>crm-schema.sql</code>:</p>
<pre><code class="language-sql">CREATE TABLE salespeople (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  first_name TEXT,
  last_name TEXT,
  email TEXT
);
</code></pre>
<p>Make the model and its migration. The model&apos;s name is singular and capitalized:</p>
<pre><code>bin/rails generate model Salesperson
</code></pre>
<p>Rails makes two files, and names the table itself:</p>
<pre><code>create    db/migrate/20261021151226_create_salespeople.rb
create    app/models/salesperson.rb
</code></pre>
<p>The table is <code>salespeople</code>, not <code>salespersons</code>. 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.</p>
<p>Open the migration. Inside the <code>create_table</code> block, add one line per column from the SQL. Leave out <code>id</code>: every table gets one without asking.</p>
<pre><code class="language-ruby">class CreateSalespeople &lt; ActiveRecord::Migration[8.1]
  def change
    create_table :salespeople do |t|
      t.string &quot;first_name&quot;
      t.string &quot;last_name&quot;
      t.string &quot;email&quot;

      t.timestamps
    end
  end
end
</code></pre>
<p><code>t.timestamps</code> was already there. It adds <code>created_at</code> and <code>updated_at</code>, which Rails fills in for you on every save. The SQL file doesn&apos;t have them; that&apos;s fine.</p>
<h2 id="activity">Activity</h2>
<pre><code class="language-sql">CREATE TABLE activities (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  salesperson_id INTEGER,
  contact_id INTEGER,
  note TEXT
);
</code></pre>
<p>Same steps:</p>
<pre><code>bin/rails generate model Activity
</code></pre>
<pre><code class="language-ruby">class CreateActivities &lt; ActiveRecord::Migration[8.1]
  def change
    create_table :activities do |t|
      t.integer &quot;salesperson_id&quot;
      t.integer &quot;contact_id&quot;
      t.string &quot;note&quot;

      t.timestamps
    end
  end
end
</code></pre>
<p>The two id columns are integers, because they hold another table&apos;s <code>id</code>, 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.</p>
<h2 id="run-them">Run them</h2>
<pre><code>bin/rails db:migrate
</code></pre>
<pre><code>== 20261021151226 CreateSalespeople: migrating ================================
-- create_table(:salespeople)
== 20261021151226 CreateSalespeople: migrated (0.0010s) =======================

== 20261021151227 CreateActivities: migrating =================================
-- create_table(:activities)
== 20261021151227 CreateActivities: migrated (0.0008s) ========================
</code></pre>
<p>Rails runs only the migrations it hasn&apos;t run yet, in order by their number. The number is the date and time you generated the file, so yours won&apos;t match these.</p>
<h2 id="check-dbschemarb">Check <code>db/schema.rb</code></h2>
<p>Rails wrote it from the migrations. Never edit it by hand; it is the record of what the migrations did.</p>
<pre><code class="language-ruby">create_table &quot;activities&quot;, force: :cascade do |t|
  t.integer &quot;contact_id&quot;
  t.datetime &quot;created_at&quot;, null: false
  t.string &quot;note&quot;
  t.integer &quot;salesperson_id&quot;
  t.datetime &quot;updated_at&quot;, null: false
end

create_table &quot;salespeople&quot;, force: :cascade do |t|
  t.datetime &quot;created_at&quot;, null: false
  t.string &quot;email&quot;
  t.string &quot;first_name&quot;
  t.string &quot;last_name&quot;
  t.datetime &quot;updated_at&quot;, null: false
end
</code></pre>
<p>The columns are listed alphabetically, with the timestamps mixed in. The order doesn&apos;t matter. What matters is that every column in <code>crm-schema.sql</code> is there with the right type, and it is.</p>
<h2 id="if-something-went-wrong">If something went wrong</h2>
<p><strong>You missed a column, and already ran the migration.</strong> Add the line to the migration file, then run <code>bin/reset-db</code>. It rebuilds the database from every migration in <code>db/migrate/</code> and deletes every row. Your files stay as they are.</p>
<p><strong><code>table &quot;salespeople&quot; already exists</code>.</strong> There are two migrations that create it, probably because you ran <code>generate model</code> twice. Delete the newer one in <code>db/migrate/</code> and run <code>bin/reset-db</code>.</p>
<p><strong>The model file is empty.</strong> It should be. <code>class Salesperson &lt; ApplicationRecord</code> and <code>end</code> is all a model needs to save, find and change rows. Lab 2 adds the rules.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Models and tests: the code-along]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><code>code-along-complete/</code> in your copy of <code>models-and-tests</code> has every step from class,<br>
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.</p>
<p>This post</p>]]></description><link>https://entr451.com/models-and-tests-code-along/</link><guid isPermaLink="false">6ad8ebdcd9019e19441c33ec</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 21 Oct 2026 16:47:45 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><code>code-along-complete/</code> in your copy of <code>models-and-tests</code> has every step from class,<br>
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.</p>
<p>This post is the map: what each step built, why it&apos;s there, and where the longer explanation lives. Read it beside the finished files.</p>
<h2 id="0-the-check-0-replayrb">0. The check: <code>0-replay.rb</code></h2>
<p>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 (<code>FAIL [7]: expected YOU WIN!, got not played yet</code>), then uncommented the game and saw six <code>PASS</code> lines.</p>
<p>That order is the week&apos;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 <code>==</code> to <code>=</code> and the check passes everything, including a broken game. The name for working this way is test-driven development.</p>
<p>Explained in full, with the same code: <a href="https://entr451.com/programming-fundamentals-lab-7-solution">Programming Fundamentals - Lab 7 - Solution</a>. The only differences are that the class file had the game pre-written and commented out, and the PASS line prints the rolls only.</p>
<h2 id="1-classes-1-classesrb">1. Classes: <code>1-classes.rb</code></h2>
<p>Every value has a class, and the class decides what it can do. <code>&quot;tacos&quot;.upcase</code>, <code>7.odd?</code>. Then a class of our own, a <code>Dog</code> that is a <code>Hash</code> (so it can hold a name) and can also <code>speak</code>:</p>
<pre><code class="language-ruby">class Dog &lt; Hash
  def speak
    &quot;woof&quot;
  end
end

daisy = Dog.new
daisy[&quot;name&quot;] = &quot;Daisy&quot;
puts daisy.speak   # woof
</code></pre>
<p>The step is there for one sentence at its end: a model is a class for one database table. <code>Company.new</code> makes one row the way <code>Dog.new</code> makes one dog; <code>company[&quot;name&quot;]</code> reads a column the way <code>daisy[&quot;name&quot;]</code> reads a key; and <code>Company.all</code> and <code>Company.where</code> are methods called on the class itself, which ask the table for rows that already exist.</p>
<p>The longer version: <a href="https://entr451.com/programming-fundamentals-classes">Programming Fundamentals - Classes</a>. It was written for an older repo, so its repository link and file names are out of date; the Ruby is the same.</p>
<h2 id="2-a-table-for-companies-2-migrations">2. A table for companies: <code>2-migrations/</code></h2>
<p>One migration, written by hand, and an empty model.</p>
<pre><code>bin/rails generate model Company
</code></pre>
<pre><code class="language-ruby">create_table :companies do |t|
  t.string &quot;name&quot;
  t.string &quot;city&quot;
  t.string &quot;state&quot;
  t.string &quot;url&quot;

  t.timestamps
end
</code></pre>
<pre><code>bin/rails db:migrate
</code></pre>
<p>A migration is the <code>CREATE TABLE</code> from week 3, as a versioned Ruby file in <code>db/migrate/</code> that Rails runs in order. <code>db/schema.rb</code> is what Rails wrote after running it; never edit it. <code>bin/reset-db</code> 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&apos;t explain.</p>
<p>The longer version: <a href="https://entr451.com/migrations">Migrations</a>.</p>
<h2 id="3-part-1-use-it-3-companiesscriptslist-companiesrb">3, part 1. Use it: <code>3-companies/scripts/list-companies.rb</code></h2>
<p>The first file the agent wrote. It deletes every company, saves three, finds one by name and changes its url, and prints them all:</p>
<pre><code class="language-ruby">apple = Company.new
apple[&quot;name&quot;] = &quot;Apple&quot;
apple[&quot;city&quot;] = &quot;Cupertino&quot;
apple.save

company = Company.find_by({ &quot;name&quot; =&gt; &quot;Anthropic&quot; })
company[&quot;url&quot;] = &quot;https://claude.ai&quot;
company.save

for company in Company.all
  puts &quot;#{company[&quot;name&quot;]}, #{company[&quot;city&quot;]}, #{company[&quot;url&quot;]}&quot;
end
</code></pre>
<p><code>app/models/company.rb</code> was still empty when this ran. Saving, finding and changing came with the table. ActiveRecord wrote the SQL (<code>INSERT</code>, <code>SELECT ... WHERE &quot;name&quot; = ...</code>, <code>UPDATE</code>) and logged it in <code>log/development.log</code>.</p>
<p>The longer version of <code>new</code>, <code>save</code>, <code>all</code>, <code>where</code> and <code>find_by</code>: <a href="https://entr451.com/models">Models</a>.</p>
<h2 id="3-part-2-and-4-the-rules-the-tests-and-the-helpful-change">3, part 2 and 4. The rules, the tests, and the helpful change</h2>
<p><code>DONE.md</code>, one sentence per rule; a test for each sentence, run red before the model changes; your check that every sentence got its test; one <code>validates</code> line that makes them green; and then a &quot;helpful&quot; change to the model that the app accepts and one test refuses.</p>
<p>Explained in full: <a href="https://entr451.com/definition-of-done-and-tests">The definition of done, and tests</a>.</p>
<h2 id="5-contacts-5-contacts">5. Contacts: <code>5-contacts/</code></h2>
<p>A second table that holds the first table&apos;s <code>id</code>, and the two lookups that make a<br>
relationship: <code>Contact.where({ &quot;company_id&quot; =&gt; apple[&quot;id&quot;] })</code> from a company to its people, <code>Company.find_by({ &quot;id&quot; =&gt; contact[&quot;company_id&quot;] })</code> from a person to their company. No <code>has_many</code>, no <code>belongs_to</code>, on purpose.</p>
<p>Explained in full: <a href="https://entr451.com/related-rows-with-where">Related rows, with where</a>.</p>
<h2 id="comparing-with-your-own-copy">Comparing with your own copy</h2>
<p>Your agent&apos;s tests and scripts came from your sentences, so they won&apos;t match these line for line. The things that should match: every sentence in your <code>DONE.md</code> 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.</p>
<p>The practice labs in <code>labs/</code> 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:<br>
<a href="https://entr451.com/models-and-tests-lab-1-solution">lab 1</a>,<br>
<a href="https://entr451.com/models-and-tests-lab-2-solution">lab 2</a>,<br>
<a href="https://entr451.com/models-and-tests-lab-3-solution">lab 3</a>,<br>
<a href="https://entr451.com/models-and-tests-lab-4-solution">lab 4</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Practice: Weather - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/8-weather.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Given the following Ruby hash that represents weather data, write a weather summary out to the screen. Something like this:</p>
<pre><code>Currently it is 67 degrees and Sunny.
Forecast for the next 7 days:
- 65 degrees and Mostly Cloudy</code></pre></blockquote>]]></description><link>https://entr451.com/practice-weather-solution/</link><guid isPermaLink="false">6ace97d3d9019e19441c33cf</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 13 Oct 2026 20:44:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/8-weather.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Given the following Ruby hash that represents weather data, write a weather summary out to the screen. Something like this:</p>
<pre><code>Currently it is 67 degrees and Sunny.
Forecast for the next 7 days:
- 65 degrees and Mostly Cloudy
- 70 degrees and Partly Cloudy
- 62 degrees and Sunny
- 68 degrees and Sunny
- 65 degrees and Partly Cloudy with a 70% chance of rain
- 72 degrees and Partly Cloudy
- 60 degrees and Rainy
</code></pre>
<p>Note: only display the chance of rain if above 50%.<br>
Note: do not display the chance of rain if conditions are already rainy.</p>
</blockquote>
<p>Run it from the top folder of the repo:</p>
<pre><code>bin/rails runner labs/8-weather.rb
</code></pre>
<p>This one uses everything: a hash, an array inside it, a loop over the array, and a conditional inside the loop.</p>
<h2 id="steps">Steps</h2>
<ol>
<li>Look at the <code>weather_data</code> hash.</li>
<li>Find the current conditions and display the first line.</li>
<li>Find the array of forecast data.</li>
<li>Loop through it, displaying one line per day.</li>
<li>Inside the loop, decide whether that day mentions the chance of rain.</li>
</ol>
<h2 id="1-look-at-the-hash-and-notice-the-keys">1. Look at the hash, and notice the keys</h2>
<pre><code class="language-ruby">weather_data = {
  current: {
    temperature: 67,
    conditions: &quot;Sunny&quot;
  },
  forecast: [
    { temperature: 65, conditions: &quot;Mostly Cloudy&quot;, precipitation: 0.4 },
    ...
    { temperature: 60, conditions: &quot;Rainy&quot;, precipitation: 0.9 }
  ]
}
</code></pre>
<p>The top level has two keys, <code>current</code> and <code>forecast</code>. <code>current</code> is a hash. <code>forecast</code> is an array of hashes, one per day: a table, with a row for each day.</p>
<p>The keys are written differently from the hashes we&apos;ve built so far. <code>current: { ... }</code> is short for <code>:current =&gt; { ... }</code>. The key is a <strong>symbol</strong>, a word that starts with a colon, not a string. The hashes code-along mentioned this at the end: two ways to write a hash. It matters here because a symbol key and a string key are different keys:</p>
<pre><code class="language-ruby">puts weather_data[&quot;current&quot;]   # nothing: there is no &quot;current&quot; key
puts weather_data[:current]    # {temperature: 67, conditions: &quot;Sunny&quot;}
</code></pre>
<p>If you get <code>nil</code>, or an error like <code>undefined method &apos;[]&apos; for nil</code>, this is the first thing to check. Use the key the way the hash was written.</p>
<h2 id="2-the-current-conditions">2. The current conditions</h2>
<p>Start with the sample line exactly as written, then swap the parts that should change:</p>
<pre><code class="language-ruby">puts &quot;Currently it is 67 degrees and Sunny.&quot;
</code></pre>
<pre><code class="language-ruby">current = weather_data[:current]
puts &quot;Currently it is #{current[:temperature]} degrees and #{current[:conditions]}.&quot;
</code></pre>
<h2 id="3-and-4-loop-through-the-forecast">3 and 4. Loop through the forecast</h2>
<p>We could pull out each day by its index, <code>weather_data[:forecast][0]</code>, then <code>[1]</code>, and so on. But that&apos;s seven nearly identical lines, and it breaks the day the forecast has eight days. A loop does it once, for however many days there are:</p>
<pre><code class="language-ruby">puts &quot;Forecast for the next #{weather_data[:forecast].size} days:&quot;
for day in weather_data[:forecast]
  puts &quot;- #{day[:temperature]} degrees and #{day[:conditions]}&quot;
end
</code></pre>
<p>Each time around, <code>day</code> is one hash from the array. <code>.size</code> counts the days, so the heading is right even if the forecast changes.</p>
<h2 id="5-the-chance-of-rain">5. The chance of rain</h2>
<p>Two notes in the problem, two conditions, and both have to be true:</p>
<ul>
<li>the chance of rain is above 50%: <code>day[:precipitation] &gt; 0.5</code></li>
<li>it isn&apos;t already rainy: <code>day[:conditions] != &quot;Rainy&quot;</code></li>
</ul>
<p><code>&amp;&amp;</code> means both. <code>!=</code> means &quot;is not equal to.&quot;</p>
<pre><code class="language-ruby">  if day[:precipitation] &gt; 0.5 &amp;&amp; day[:conditions] != &quot;Rainy&quot;
    chance_of_rain = (day[:precipitation] * 100).round
    puts &quot;- #{day[:temperature]} degrees and #{day[:conditions]} with a #{chance_of_rain}% chance of rain&quot;
  else
    puts &quot;- #{day[:temperature]} degrees and #{day[:conditions]}&quot;
  end
</code></pre>
<p><code>precipitation</code> is stored as a fraction, <code>0.7</code>, so we multiply by 100 for a percentage. <code>0.7 * 100</code> is <code>70.0</code>, a Float, which would print as &quot;70.0%&quot;; <code>.round</code> makes it a whole number.</p>
<p>Check the output against the notes. Day 5 (0.7, Partly Cloudy) mentions rain. Day 7 (0.9) has a higher chance, but it&apos;s already Rainy, so it doesn&apos;t. Day 1 (0.4) is under 50%.</p>
<h2 id="final-code">Final code</h2>
<pre><code class="language-ruby"># 1. inspect the weather_data hash
# puts weather_data

# 2. the current conditions; the keys are symbols, so :current, not &quot;current&quot;
current = weather_data[:current]
puts &quot;Currently it is #{current[:temperature]} degrees and #{current[:conditions]}.&quot;

# 3. the forecast is an array of hashes, one per day
puts &quot;Forecast for the next #{weather_data[:forecast].size} days:&quot;

# 4. loop through it, one line per day
for day in weather_data[:forecast]

  # 5. mention the chance of rain only if it&apos;s above 50% and it isn&apos;t already rainy
  if day[:precipitation] &gt; 0.5 &amp;&amp; day[:conditions] != &quot;Rainy&quot;
    chance_of_rain = (day[:precipitation] * 100).round
    puts &quot;- #{day[:temperature]} degrees and #{day[:conditions]} with a #{chance_of_rain}% chance of rain&quot;
  else
    puts &quot;- #{day[:temperature]} degrees and #{day[:conditions]}&quot;
  end
end
</code></pre>
<p>Output:</p>
<pre><code>Currently it is 67 degrees and Sunny.
Forecast for the next 7 days:
- 65 degrees and Mostly Cloudy
- 70 degrees and Partly Cloudy
- 62 degrees and Sunny
- 68 degrees and Sunny
- 65 degrees and Partly Cloudy with a 70% chance of rain
- 72 degrees and Partly Cloudy
- 60 degrees and Rainy
</code></pre>
<h2 id="challenge">Challenge</h2>
<p>Add a line under the forecast that says which day will be the warmest, something like &quot;The warmest day will be day 6, at 72 degrees.&quot; You&apos;ll need a variable that remembers the highest temperature so far, set before the loop and updated inside it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>