<?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>Wed, 21 Oct 2026 02:16:06 GMT</lastBuildDate><atom:link href="https://entr451.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><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><item><title><![CDATA[Programming Fundamentals - Lab 7 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/7-replay.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>The game in <code>6-craps.rb</code> is random, so running it a few times proves very little. Here is the same game with one change: instead of rolling dice, it reads each total from a list of rolls written down</p></blockquote>]]></description><link>https://entr451.com/programming-fundamentals-lab-7-solution/</link><guid isPermaLink="false">6ace972dd9019e19441c33a9</guid><category><![CDATA[Programming Fundamentals Lab Solution]]></category><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 13 Oct 2026 20:41:47 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/7-replay.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>The game in <code>6-craps.rb</code> is random, so running it a few times proves very little. Here is the same game with one change: instead of rolling dice, it reads each total from a list of rolls written down ahead of time. Same rolls in, same result out, every time. That can be checked.</p>
</blockquote>
<p>Run it from the top folder of the repo:</p>
<pre><code>bin/rails runner labs/7-replay.rb
</code></pre>
<p>The lab comes in four steps, and the order matters: the check is written, and seen to fail, before the game goes in.</p>
<h2 id="1-decide-what-right-looks-like">1. Decide what right looks like</h2>
<p>The file starts with one game: a list of rolls, and the result you expect.</p>
<pre><code class="language-ruby">games = [
  { &quot;rolls&quot; =&gt; [7], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; }
]
</code></pre>
<p>It&apos;s an array of hashes, like the teams table: one hash per game. Your job is to add games, and to decide each expected result from the rules, before you run anything. Which lists of rolls would test every rule?</p>
<pre><code class="language-ruby">games = [
  { &quot;rolls&quot; =&gt; [7], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [12], &quot;expected&quot; =&gt; &quot;YOU LOSE!&quot; },
  { &quot;rolls&quot; =&gt; [8, 4, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [8, 7], &quot;expected&quot; =&gt; &quot;YOU LOSE!&quot; },
  { &quot;rolls&quot; =&gt; [8, 11, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [8, 2, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; }
]
</code></pre>
<p>Each game is there for a rule:</p>
<table>
<thead>
<tr>
<th>Rolls</th>
<th>Tests</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[7]</code></td>
<td>7 on the first roll wins</td>
</tr>
<tr>
<td><code>[12]</code></td>
<td>12 on the first roll loses</td>
</tr>
<tr>
<td><code>[8, 4, 8]</code></td>
<td>the point comes up again: win</td>
</tr>
<tr>
<td><code>[8, 7]</code></td>
<td>a 7 after the point: lose</td>
</tr>
<tr>
<td><code>[8, 11, 8]</code></td>
<td>an 11 after the point does nothing</td>
</tr>
<tr>
<td><code>[8, 2, 8]</code></td>
<td>a 2 after the point does nothing</td>
</tr>
</tbody>
</table>
<p>The last two are the ones random play almost never shows you, and the ones a chat most often gets wrong. You could add <code>[11]</code>, <code>[2]</code> and <code>[3]</code> too; each is a rule of its own.</p>
<h2 id="2-and-3-write-the-check-and-watch-it-fail">2 and 3. Write the check, and watch it fail</h2>
<p>The file loops through the games with <code>for game in games</code>, and starts each one with <code>result = &quot;not played yet&quot;</code>. Before the game goes in, write the check: the result we got against the result we expected.</p>
<pre><code class="language-ruby">  if result == game[&quot;expected&quot;]
    puts &quot;PASS: #{rolls} #{result}&quot;
  else
    raise &quot;FAIL: #{rolls} expected #{game[&quot;expected&quot;]}, got #{result}&quot;
  end
</code></pre>
<p><code>raise</code> stops the program with an error, and the error says what you told it to say. Run the file now, with no game in it:</p>
<pre><code>FAIL: [7] expected YOU WIN!, got not played yet (RuntimeError)
</code></pre>
<p>That&apos;s the point of this step. Nothing has played, so the check <em>should</em> fail, and it does. A check you have never seen fail might not be checking anything.</p>
<p>When you see an error, read its first line: that&apos;s the message. The lines under it, starting with <code>from</code>, show where the program was when it stopped. Most of them are Rails, not you.</p>
<h2 id="4-put-the-game-in">4. Put the game in</h2>
<p>Paste the game from <code>6-craps.rb</code> above the check and change one thing: where each total comes from. Instead of <code>rand</code>, read the next number from <code>rolls</code>. An <code>index</code> that starts at 0 keeps track of which roll is next:</p>
<pre><code class="language-ruby">  total = rolls[index]
  index = index + 1
</code></pre>
<p>And instead of <code>puts &quot;YOU WIN!&quot;</code>, save it: <code>result = &quot;YOU WIN!&quot;</code>, so the check can compare it. A chat can make this change for you:</p>
<pre><code>Here is my craps game in plain Ruby:

[paste your game from labs/6-craps.rb here]

Change one thing: instead of rolling dice, each total should come from a list called rolls, read in order with an index that starts at 0. Don&apos;t print anything. Save &quot;YOU WIN!&quot; or &quot;YOU LOSE!&quot; in a variable called result. If the list runs out before the game ends, raise an error that says so, so it can&apos;t loop forever.

Keep everything else the same: the same rules, plain Ruby, no methods, no classes. Give me just the code, to paste inside a loop that already sets rolls.
</code></pre>
<p>That &quot;runs out&quot; line is worth having. <code>[8, 4]</code> sets a point and never ends the game. With dice, you&apos;d just roll again. With a list, <code>rolls[2]</code> is <code>nil</code>, which is neither the point nor a 7, so the loop would go around forever. Checking for <code>nil</code> turns that into an error you can read:</p>
<pre><code>FAIL: [8, 4] ran out of rolls before the game ended (RuntimeError)
</code></pre>
<p>Run the file again. Six games, six PASS.</p>
<h2 id="final-code">Final code</h2>
<pre><code class="language-ruby"># 1. Games to replay: the rolls, and the result you expect.
games = [
  { &quot;rolls&quot; =&gt; [7], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [12], &quot;expected&quot; =&gt; &quot;YOU LOSE!&quot; },
  { &quot;rolls&quot; =&gt; [8, 4, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [8, 7], &quot;expected&quot; =&gt; &quot;YOU LOSE!&quot; },
  { &quot;rolls&quot; =&gt; [8, 11, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; },
  { &quot;rolls&quot; =&gt; [8, 2, 8], &quot;expected&quot; =&gt; &quot;YOU WIN!&quot; }
]

# 2. Replay each game
for game in games
  rolls = game[&quot;rolls&quot;]
  result = &quot;not played yet&quot;
  index = 0

  # 4. The game, as in 6-craps.rb, except where each total comes from
  total = rolls[index]
  index = index + 1

  if total == 7 || total == 11
    result = &quot;YOU WIN!&quot;
  elsif total == 2 || total == 3 || total == 12
    result = &quot;YOU LOSE!&quot;
  else
    point = total

    loop do
      total = rolls[index]
      index = index + 1

      # a game that runs out of rolls would otherwise loop forever
      if total == nil
        raise &quot;FAIL: #{rolls} ran out of rolls before the game ended&quot;
      end

      if total == point
        result = &quot;YOU WIN!&quot;
        break
      elsif total == 7
        result = &quot;YOU LOSE!&quot;
        break
      end
    end
  end

  # 3. Check: the result we got against the result we expected.
  #    Written and run before the game was in, so we saw it fail first.
  if result == game[&quot;expected&quot;]
    puts &quot;PASS: #{rolls} #{result}&quot;
  else
    raise &quot;FAIL: #{rolls} expected #{game[&quot;expected&quot;]}, got #{result}&quot;
  end
end
</code></pre>
<p>Output:</p>
<pre><code>PASS: [7] YOU WIN!
PASS: [12] YOU LOSE!
PASS: [8, 4, 8] YOU WIN!
PASS: [8, 7] YOU LOSE!
PASS: [8, 11, 8] YOU WIN!
PASS: [8, 2, 8] YOU WIN!
</code></pre>
<p>Run it again. And again. The same six lines every time. That&apos;s what makes it checkable.</p>
<h2 id="try-breaking-it">Try breaking it</h2>
<p>Two experiments, to see why step 3 came first.</p>
<p><strong>Break the game.</strong> Where the point comes up, change <code>result = &quot;YOU WIN!&quot;</code> to <code>result = &quot;YOU LOSE!&quot;</code>. Run it:</p>
<pre><code>PASS: [7] YOU WIN!
PASS: [12] YOU LOSE!
FAIL: [8, 4, 8] expected YOU WIN!, got YOU LOSE! (RuntimeError)
</code></pre>
<p>The check caught it, and told you which game and what it got. Put it back.</p>
<p><strong>Break the check.</strong> In the check, change <code>==</code> to <code>=</code>, so the line reads<br>
<code>if result = game[&quot;expected&quot;]</code>. Run it: six PASS. Now break the game again the same way. Still six PASS.</p>
<p>One <code>=</code> doesn&apos;t compare; it <em>sets</em> <code>result</code> to the expected value, and then the PASS line prints that. The game is wrong and every line says it&apos;s fine. If this check had been written after the game, and passed the first time, you&apos;d have no reason to doubt it. Because you watched it fail when nothing had played, you know it can.</p>
<p>If a check fails and you&apos;re sure the game is right, look at the expected result too. Deciding what right looks like is part of the job, and it can be wrong.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Programming Fundamentals - Lab 4 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/4-hashes.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Start with an amount of Bitcoin that someone owns. Using the Ruby hash <code>bitcoin_data</code>, display a summary of Bitcoin data for the user. Something similar to the sample output.</p>
<p>Sample output:<br>
1 Bitcoin is valued at $41405.1046</p></blockquote>]]></description><link>https://entr451.com/programming-fundamentals-lab-4-solution/</link><guid isPermaLink="false">6ace9615d9019e19441c337d</guid><category><![CDATA[Programming Fundamentals Lab Solution]]></category><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 13 Oct 2026 20:39:48 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/4-hashes.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Start with an amount of Bitcoin that someone owns. Using the Ruby hash <code>bitcoin_data</code>, display a summary of Bitcoin data for the user. Something similar to the sample output.</p>
<p>Sample output:<br>
1 Bitcoin is valued at $41405.1046 USD.<br>
Your Bitcoin is worth $62107.6569.</p>
</blockquote>
<p>Run it from the top folder of the repo (where <code>README.md</code> is):</p>
<pre><code>bin/rails runner labs/4-hashes.rb
</code></pre>
<h2 id="the-code-you-dont-change">The code you don&apos;t change</h2>
<p>The top of the file asks a live service for the current price of Bitcoin:</p>
<pre><code class="language-ruby"># DON&apos;T CHANGE THIS CODE
# ----------------------
require &quot;net/http&quot;
require &quot;json&quot;
url = &quot;https://blockchain.info/ticker&quot;
uri = URI(url)
response = Net::HTTP.get(uri)
bitcoin_data = JSON.parse(response)
# ----------------------
</code></pre>
<p>We&apos;ll learn more about APIs later. For now: an API is how another piece of software shares its data with your code. A person would visit a website to see the price of Bitcoin; code asks the API instead. These lines go to that URL, get back text, turn the text into a Ruby hash, and save it in <code>bitcoin_data</code>. Everything after this is a hash, which you already know how to work with.</p>
<h2 id="1-and-2-how-much-bitcoin-as-a-number">1 and 2. How much Bitcoin, as a number</h2>
<p>The file starts you off:</p>
<pre><code class="language-ruby">bitcoin = &quot;1.5&quot;
bitcoin = bitcoin.to_f
</code></pre>
<p>The amount is in quotes on purpose. Anything a person types into a form or a terminal reaches your code as a string, even when it looks like a number. A string can&apos;t be multiplied, so <code>.to_f</code> turns it into a Float (a number with a decimal point). Change <code>&quot;1.5&quot;</code> to any amount you like.</p>
<h2 id="3-look-at-the-hash-before-you-dig-into-it">3. Look at the hash before you dig into it</h2>
<pre><code class="language-ruby">puts bitcoin_data
</code></pre>
<p>You&apos;ll see something like this (shortened, and your numbers will be today&apos;s):</p>
<pre><code class="language-ruby">{
  ...
  &quot;EUR&quot; =&gt; { &quot;15m&quot; =&gt; 74258.37, &quot;last&quot; =&gt; 74258.37, &quot;buy&quot; =&gt; 74258.37, &quot;sell&quot; =&gt; 74258.37, &quot;symbol&quot; =&gt; &quot;EUR&quot; },
  ...
  &quot;USD&quot; =&gt; { &quot;15m&quot; =&gt; 83165.11, &quot;last&quot; =&gt; 83165.11, &quot;buy&quot; =&gt; 83165.11, &quot;sell&quot; =&gt; 83165.11, &quot;symbol&quot; =&gt; &quot;USD&quot; }
}
</code></pre>
<p>It&apos;s a hash (the curly braces), with one key per currency. The value of each key is another hash. Look at just the one we want:</p>
<pre><code class="language-ruby">puts bitcoin_data[&quot;USD&quot;]
</code></pre>
<p>There are a few prices in there. <code>&quot;last&quot;</code> is the most recent one, and it has no quotes around it, so it&apos;s already a number we can do math with.</p>
<h2 id="4-dig-out-the-price-do-the-math-display-it">4. Dig out the price, do the math, display it</h2>
<pre><code class="language-ruby">usd_rate = bitcoin_data[&quot;USD&quot;][&quot;last&quot;]
puts usd_rate
</code></pre>
<p>It never hurts to <code>puts</code> a value as a sanity check before you use it. Then the math:</p>
<pre><code class="language-ruby">usd_value = bitcoin * usd_rate
</code></pre>
<p>For the output, start with the sample text exactly as written, then swap the parts that should change for your variables:</p>
<pre><code class="language-ruby">puts &quot;1 Bitcoin is valued at $41405.1046 USD.&quot;
puts &quot;Your Bitcoin is worth $62107.6569.&quot;
</code></pre>
<p>becomes</p>
<pre><code class="language-ruby">puts &quot;1 Bitcoin is valued at $#{usd_rate} USD.&quot;
puts &quot;Your Bitcoin is worth $#{usd_value.round(2)}.&quot;
</code></pre>
<p><code>.round(2)</code> keeps two decimal places, the way money is usually shown. Without it you may see a long tail of digits.</p>
<h2 id="final-code">Final code</h2>
<pre><code class="language-ruby"># 1. How much Bitcoin, as a string, the way typed input arrives
bitcoin = &quot;1.5&quot;

# 2. A string can&apos;t be multiplied: make it a Float
bitcoin = bitcoin.to_f

# 3. Inspect the hash: one key per currency, each a hash of its own
# puts bitcoin_data
# puts bitcoin_data[&quot;USD&quot;]

# 4. Dig out the price, then do the math and display it
usd_rate = bitcoin_data[&quot;USD&quot;][&quot;last&quot;]
usd_value = bitcoin * usd_rate

puts &quot;1 Bitcoin is valued at $#{usd_rate} USD.&quot;
puts &quot;Your Bitcoin is worth $#{usd_value.round(2)}.&quot;
</code></pre>
<p>Output (with today&apos;s price):</p>
<pre><code>1 Bitcoin is valued at $83165.11 USD.
Your Bitcoin is worth $124747.67.
</code></pre>
<h2 id="challenge">Challenge</h2>
<p>Could you take this a step further? Add a variable for the currency, <code>currency = &quot;EUR&quot;</code>, and show the price and the value in that currency instead. If the currency isn&apos;t one the API knows (try <code>&quot;XYZ&quot;</code>), show a message instead of an error.</p>
<p>A hint: when you ask a hash for a key it doesn&apos;t have, you get <code>nil</code> back.</p>
<pre><code class="language-ruby">currency = &quot;EUR&quot;
currency_data = bitcoin_data[currency]

if currency_data == nil
  puts &quot;Sorry, #{currency} isn&apos;t a currency we have a price for.&quot;
else
  rate = currency_data[&quot;last&quot;]
  value = bitcoin * rate
  puts &quot;1 Bitcoin is valued at #{rate} #{currency}.&quot;
  puts &quot;Your Bitcoin is worth #{value.round(2)} #{currency}.&quot;
end
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Programming Fundamentals - Lab 6 - Solution]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/6-craps.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Craps: the whole game. The rules:</p>
<ul>
<li>The first roll: 7 or 11 wins. 2, 3 or 12 loses. Anything else becomes the point.</li>
<li>Once there is a point, keep rolling until:
<ul>
<li>the point comes up again: the player wins</li></ul></li></ul></blockquote>]]></description><link>https://entr451.com/programming-fundamentals-lab-6-solution/</link><guid isPermaLink="false">6ace96aed9019e19441c3391</guid><category><![CDATA[Programming Fundamentals Lab Solution]]></category><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 13 Oct 2026 20:39:39 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="the-problem">The problem</h2>
<p>From <code>labs/6-craps.rb</code> in your copy of <code>ruby-intro</code>:</p>
<blockquote>
<p>Craps: the whole game. The rules:</p>
<ul>
<li>The first roll: 7 or 11 wins. 2, 3 or 12 loses. Anything else becomes the point.</li>
<li>Once there is a point, keep rolling until:
<ul>
<li>the point comes up again: the player wins</li>
<li>a 7 comes up: the player loses</li>
</ul>
</li>
<li>After the point is set, nothing else matters. Any other total means roll again.</li>
</ul>
<p>Show each roll (&quot;Roll 1: 8&quot;), the point once it&apos;s set, then &quot;YOU WIN!&quot; or &quot;YOU LOSE!&quot;.</p>
</blockquote>
<p>Run it from the top folder of the repo:</p>
<pre><code>bin/rails runner labs/6-craps.rb
</code></pre>
<p>The first roll is already written. It&apos;s lab 2 with each roll numbered:</p>
<pre><code class="language-ruby">roll_number = 1
total = rand(1..6) + rand(1..6)
puts &quot;Roll #{roll_number}: #{total}&quot;

if total == 7 || total == 11
  puts &quot;YOU WIN!&quot;
elsif total == 2 || total == 3 || total == 12
  puts &quot;YOU LOSE!&quot;
else
  point = total
  puts &quot;THE POINT IS #{point}&quot;

  # what happens after the point is set goes here

end
</code></pre>
<p>The only part left is the <code>else</code> branch, after the point is set. That&apos;s the new idea: until now every program ran top to bottom once. This one has to keep going until something happens, and you can&apos;t know in advance how many rolls that will take.</p>
<h2 id="1-pseudo-code-first">1. Pseudo-code first</h2>
<p>Before any code, write what happens after the point in plain English, one step per line, as comments:</p>
<pre><code class="language-ruby"># 1. roll again, and show the total
# 2. if it&apos;s the point, the player wins: stop
# 3. if it&apos;s a 7, the player loses: stop
# 4. anything else: nothing happens, go back to step 1
</code></pre>
<p>Check yours against the rules. The gaps that show up most often:</p>
<ul>
<li><strong>No &quot;keep rolling.&quot;</strong> One more roll, then done. The rules say roll <em>until</em>.</li>
<li><strong>No way to stop.</strong> Rolling forever is the opposite mistake.</li>
<li><strong>Nothing about the other totals.</strong> What happens to an 11 after the point? A 2? The rules say nothing: they mean roll again. A first-roll rule (11 wins, 2 loses) creeping into the point phase is the most common bug in this lab, written by people and by chats alike.</li>
</ul>
<h2 id="2-ask-a-chat-for-the-code">2. Ask a chat for the code</h2>
<p>Give a chat the rules, your pseudo-code and the file, and say exactly what you want back. Something like:</p>
<pre><code>I&apos;m writing a craps game in plain Ruby. The rules:
- First roll: 7 or 11 wins. 2, 3 or 12 loses. Anything else becomes the point.
- Once there is a point, keep rolling until the point comes up again (the player wins) or a 7 comes up (the player loses).
- After the point is set, nothing else matters. Any other total, including 2, 3, 11 or 12, means roll again.

My pseudo-code for what happens after the point:
# 1. roll again, and show the total
# 2. if it&apos;s the point, the player wins: stop
# 3. if it&apos;s a 7, the player loses: stop
# 4. anything else: nothing happens, go back to step 1

My file is below. The first roll is already written. Fill in only the else branch, under my pseudo-code, with the code for each step under its comment.

Use only what the file already uses: variables, if/elsif/else, loop do ... end with break, and puts. No methods, no classes, no gets. Print each roll the way the first roll does (&quot;Roll 2: 9&quot;), then &quot;YOU WIN!&quot; or &quot;YOU LOSE!&quot;. Give me just the code for the else branch.

[paste labs/6-craps.rb here]
</code></pre>
<p>The last paragraph matters. Without it, a chat will often hand back a method, a class, or code that waits for you to press Enter between rolls (<code>gets</code>). None of that is wrong, but it&apos;s not what you asked for, and it&apos;s harder to read.</p>
<h2 id="3-the-loop">3. The loop</h2>
<p>Each line of pseudo-code becomes a line or two of code under its comment.</p>
<p><code>loop do ... end</code> repeats everything inside it, forever, until it reaches <code>break</code>. So the two ways the game ends are the two places we <code>break</code>:</p>
<pre><code class="language-ruby">  loop do
    # 1. roll again, and show the total
    roll_number = roll_number + 1
    total = rand(1..6) + rand(1..6)
    puts &quot;Roll #{roll_number}: #{total}&quot;

    # 2. if it&apos;s the point, the player wins: stop
    if total == point
      puts &quot;YOU WIN!&quot;
      break
    # 3. if it&apos;s a 7, the player loses: stop
    elsif total == 7
      puts &quot;YOU LOSE!&quot;
      break
    end
    # 4. anything else: nothing happens, go back to step 1
  end
</code></pre>
<p>A few things to notice:</p>
<ul>
<li><strong><code>roll_number = roll_number + 1</code></strong> comes before the roll, so the second roll prints as &quot;Roll 2&quot;. <code>roll_number</code> was set to 1 above, outside the loop, so it keeps counting up.</li>
<li><strong><code>total</code> is reused.</strong> We don&apos;t need the first roll&apos;s total any more; <code>point</code> remembers it.</li>
<li><strong>Step 4 has no code.</strong> If the total is neither the point nor a 7, nothing in the <code>if</code> runs, we reach the bottom of the loop, and it goes around again. That&apos;s how an 11 or a 2 after the point &quot;does nothing.&quot; Saying it in the pseudo-code is still worth it: it&apos;s the step that keeps the first-roll rules out.</li>
</ul>
<p>If you run it and it never stops, press Ctrl+C. Most likely a <code>break</code> is missing.</p>
<h2 id="final-code">Final code</h2>
<pre><code class="language-ruby">roll_number = 1
total = rand(1..6) + rand(1..6)
puts &quot;Roll #{roll_number}: #{total}&quot;

if total == 7 || total == 11
  puts &quot;YOU WIN!&quot;
elsif total == 2 || total == 3 || total == 12
  puts &quot;YOU LOSE!&quot;
else
  point = total
  puts &quot;THE POINT IS #{point}&quot;

  loop do
    # 1. roll again, and show the total
    roll_number = roll_number + 1
    total = rand(1..6) + rand(1..6)
    puts &quot;Roll #{roll_number}: #{total}&quot;

    # 2. if it&apos;s the point, the player wins: stop
    if total == point
      puts &quot;YOU WIN!&quot;
      break
    # 3. if it&apos;s a 7, the player loses: stop
    elsif total == 7
      puts &quot;YOU LOSE!&quot;
      break
    end
    # 4. anything else: nothing happens, go back to step 1
  end
end
</code></pre>
<p>One run:</p>
<pre><code>Roll 1: 9
THE POINT IS 9
Roll 2: 8
Roll 3: 6
Roll 4: 9
YOU WIN!
</code></pre>
<p>Your chat may have written it differently: a <code>while</code> loop, two loops, a method. Same rules, different code. If it follows the rules, it&apos;s right.</p>
<h2 id="so-is-it-right">So, is it right?</h2>
<p>How do you know? Most of us ran it a few times, and it seemed to work. But each run, the dice decided which case you got to see. Did you see a point followed by an 11? A point followed by a 2? Running a random program again isn&apos;t checking it. Lab 7 takes the dice out so the game can be checked.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[CRM: data model to database]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>In Week 3 you drew the CRM&apos;s data model on paper. This is how that drawing becomes real tables: a chat writes the CREATE TABLE statements, you load them, and you check the result against your drawing. It is the same loop as Assignment 1 phase 1, step</p></blockquote>]]></description><link>https://entr451.com/crm-data-model-to-database/</link><guid isPermaLink="false">6acd34eed9019e19441c334a</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Mon, 12 Oct 2026 19:32:24 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>In Week 3 you drew the CRM&apos;s data model on paper. This is how that drawing becomes real tables: a chat writes the CREATE TABLE statements, you load them, and you check the result against your drawing. It is the same loop as Assignment 1 phase 1, step 4, on a smaller model.</p>
</blockquote>
<h2 id="the-repository">The repository</h2>
<p><code>github.com/dsgn425-fall2026/crm</code>. Click <strong>Use this template</strong>, then <strong>Create a new repository</strong> in your own account. In your copy: <strong>Code</strong>, then <strong>Codespaces</strong>, then <strong>Create codespace on main</strong>. The first start takes about three minutes.</p>
<p>Its README has the stories, the steps, and what every file is for. The short version follows.</p>
<h2 id="the-steps">The steps</h2>
<ol>
<li>
<p>In the terminal, run <code>bin/rails server</code> and open the port it offers. Then open a second terminal with the <strong>+</strong> on the terminal panel; the server keeps the first one busy.</p>
</li>
<li>
<p>Give a chat a photo of your ERD (or the stories in the README) and ask for SQLite <code>CREATE TABLE</code> statements. Paste them into <code>schema/crm.sql</code>.</p>
</li>
<li>
<p>In the second terminal:</p>
<pre><code>bin/load
</code></pre>
<p>It rebuilds the database from nothing, from <code>schema/crm.sql</code>, and lists the tables it made. If a statement has an error, it stops, names the line, and leaves the database as it was.</p>
</li>
<li>
<p>Open the <strong>Schema</strong> page. Is each table in your drawing there, with the columns you drew? Does each foreign key point where you meant it to? Where the chat differs from your drawing, decide who is right. In Assignment 1 the chat reviews your drawing before it writes any SQL, and <code>NOTES.md</code> records what it would change and what you took.</p>
</li>
</ol>
<p><code>solutions/crm.sql</code> has one possible answer. Compare it once you have your own.</p>
<h2 id="why-the-terminal-not-the-run-sql-box">Why the terminal, not the Run SQL box</h2>
<p>Changing a database&apos;s structure (<code>CREATE TABLE</code>, <code>ALTER TABLE</code>, <code>DROP TABLE</code>) never happens inside an app. The Run SQL box is for reading data. An occasional <code>INSERT</code>, <code>UPDATE</code> or <code>DELETE</code> is closer to what apps do every day, but tables are built from a file, through the terminal. Paste a <code>CREATE TABLE</code> into the box and it refuses, and tells you where it goes instead.</p>
<p>It is the same idea as the database user roles from Week 2: read data, write data, change the structure. Most people need only the first. If your job is analyzing data, ask for a read-only login. Not because anyone thinks you can&apos;t be trusted; so that a slip can&apos;t break anything.</p>
<p>Assignment 1 works the same way: <code>kmdb</code> is the same app, with <code>schema/kmdb.sql</code>, <code>data/kmdb.sql</code>, and <code>bin/load</code>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Domain Modeling: The Data Model]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In class, you asked a baseball database which team has lost the most games in history, and it couldn&apos;t really answer. It could give you the worst single season (the 1899 Cleveland Spiders, 20-134) or the most losses added up by team name (the Philadelphia Phillies, 10,426)</p>]]></description><link>https://entr451.com/domain-modeling-the-data-model/</link><guid isPermaLink="false">6abc113ad9019e19441c333f</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 19:28:25 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In class, you asked a baseball database which team has lost the most games in history, and it couldn&apos;t really answer. It could give you the worst single season (the 1899 Cleveland Spiders, 20-134) or the most losses added up by team name (the Philadelphia Phillies, 10,426). But a fan asking that question means the <em>franchise</em>, and there is no franchise anywhere in the database. Cleveland alone shows up under seven different names.</p>
<p>No amount of clever SQL fixes that. The problem isn&apos;t the query; it&apos;s the design of the tables. Deciding what tables a system should have, and how they relate to each other, is called <strong>domain modeling</strong>, and it&apos;s the subject of this post.</p>
<h3 id="models">Models</h3>
<p>A domain is the slice of the real world your software is about: a school, an airline, a store, a baseball league. A domain model represents that world in software, using the words the people in it actually use. An airline and a retailer end up with different models because they talk about different things.</p>
<p>We&apos;ll use a school as our example. We call each real-world thing in the domain a <strong>model</strong>, and all of the models, together with the relationships between them, make up the <strong>domain model</strong>.</p>
<p>What are the real, tangible things in a school? Two obvious ones:</p>
<ul>
<li>Student</li>
<li>Teacher</li>
</ul>
<p>Students and teachers aren&apos;t theoretical. They&apos;re people who exist outside the software. Students attend the school; teachers teach its courses.</p>
<p>You might ask: why not a single model for Person? Students and teachers are both people. It&apos;s a fair question, and the answer is that models are distinguished by their <strong>attributes</strong>, the data we need to keep about them. In our school, teachers have a bio and students don&apos;t. Students have contact information (email and phone number) and teachers don&apos;t. So they&apos;re different models.</p>
<p>Where do those attributes come from? From what the product needs to do. If the product shows a teacher&apos;s bio on the course page, a teacher needs a bio. Next week, we&apos;ll write that down properly as user stories, and the attributes will fall out of them. For now:</p>
<ul>
<li>Student: first name, last name, email, phone number</li>
<li>Teacher: first name, last name, bio</li>
</ul>
<h3 id="models-become-tables">Models become tables</h3>
<p>If you&apos;ve been following along with SQL, you&apos;ve probably already guessed where this is going. A model and its attributes map neatly onto a database table:</p>
<ul>
<li>Each <strong>column</strong> is an attribute</li>
<li>Each <strong>row</strong> is one instance of the model: one student, one teacher</li>
</ul>
<p>Each model gets its own table, because each model has different attributes:</p>
<p><strong>students</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>first_name</th>
<th>last_name</th>
<th>email</th>
<th>phone_number</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Jane</td>
<td>Doe</td>
<td><a href="mailto:jane@example.com">jane@example.com</a></td>
<td>555-1212</td>
</tr>
<tr>
<td>2</td>
<td>Jenny</td>
<td>Smith</td>
<td><a href="mailto:jenny@gmail.com">jenny@gmail.com</a></td>
<td>867-5309</td>
</tr>
<tr>
<td>3</td>
<td>John</td>
<td>Johnson</td>
<td><a href="mailto:john@acme.com">john@acme.com</a></td>
<td>456-7890</td>
</tr>
</tbody>
</table>
<p><strong>teachers</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>first_name</th>
<th>last_name</th>
<th>bio</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Ben</td>
<td>Block</td>
<td>Often talks to a rubber ducky.</td>
</tr>
<tr>
<td>2</td>
<td>Brian</td>
<td>Eng</td>
<td>Loves tacos.</td>
</tr>
</tbody>
</table>
<p>A good start. Next, a Course. A course isn&apos;t a physical thing you can hold, the way a student or teacher is, but it&apos;s real and it has data we need to keep: a name and a description.</p>
<p><strong>courses</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Intro to Software Development</td>
<td>This course is focused on software development...</td>
</tr>
<tr>
<td>2</td>
<td>Taco-Making 101</td>
<td>In this course, you&apos;ll learn how to build a proper taco...</td>
</tr>
</tbody>
</table>
<h3 id="an-attribute-or-a-new-model">An attribute, or a new model?</h3>
<p>You might be wondering whether that&apos;s really all the data a course needs. What about the times it meets, and who teaches it?</p>
<p>Knowing what belongs as an attribute on a model and what should be its own model is one of the keys to being good at domain modeling. So let&apos;s try putting the times and teachers on the course and see what happens:</p>
<p><strong>courses</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>description</th>
<th>time_1</th>
<th>teacher_id_1</th>
<th>time_2</th>
<th>teacher_id_2</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Intro to Software Development</td>
<td>This course is...</td>
<td>Wednesday 6:30-9:30pm</td>
<td>1</td>
<td>Friday 1:30-4:30pm</td>
<td>2</td>
</tr>
<tr>
<td>2</td>
<td>Taco-Making 101</td>
<td>In this course...</td>
<td>Wednesday 6-9pm</td>
<td>2</td>
<td>Thursday 6-9pm</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>Seems reasonable, until Taco-Making gets popular and needs a third time. Then a fourth. Every new time slot means two new columns, and every course that has fewer slots is left with blank cells. The table is growing <strong>sideways</strong>.</p>
<p>That&apos;s the smell. A well-designed table grows <strong>down</strong>: new data means new rows, not new columns. When a table keeps needing more columns for &quot;another one of these,&quot; that thing wants to be its own model.</p>
<h3 id="one-to-many">One-to-many</h3>
<p>Each time a course is offered, with its own teacher, is a <strong>Section</strong>. One course can have many sections:</p>
<p><strong>sections</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>time</th>
<th>course_id</th>
<th>teacher_id</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Wednesday 6:30-9:30pm</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>Friday 1:30-4:30pm</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>Wednesday 6-9pm</td>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>4</td>
<td>Thursday 6-9pm</td>
<td>2</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>Much better. Courses now only hold what&apos;s true of the course itself, and sections hold the rest. A new section is just a new row, and a course can have none, one, or a hundred.</p>
<p>Section makes sense as its own model because we don&apos;t know how many there will be, and because it has attributes of its own (a time, a teacher) that might grow later.</p>
<p>This is a <strong>one-to-many relationship</strong>, and the way you build one is the foreign key you met in SQL 2: put a column on the &quot;many&quot; side that points at the &quot;one&quot; side. One course has many sections, so <code>course_id</code> goes on <code>sections</code>.</p>
<h3 id="many-to-many">Many-to-many</h3>
<p>Notice that <code>sections</code> has two foreign keys. That&apos;s because there are two one-to-many relationships here: a course has many sections, and a teacher has many sections.</p>
<p>Put those together and you get something new. A course can have many teachers, and a teacher can teach many courses. That&apos;s a <strong>many-to-many relationship</strong>, and it never lives directly on either table. It&apos;s always two one-to-manys through a model in the middle:</p>
<blockquote>
<p>Two one-to-many relationships = one many-to-many relationship</p>
</blockquote>
<p>The model in the middle is called a <strong>join model</strong>. Here, it&apos;s Section. Unlike Student or Teacher, Section isn&apos;t really a thing you&apos;d point at in the physical world; it exists because of the relationship between two things that are.</p>
<h3 id="back-to-the-franchise">Back to the franchise</h3>
<p>Now the baseball question has an answer. The database was missing a model:</p>
<p><strong>franchises</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr>
<td>...</td>
<td>Cleveland</td>
</tr>
</tbody>
</table>
<p><strong>teams</strong> (one row per team, per season)</p>
<table>
<thead>
<tr>
<th>id</th>
<th>franchise_id</th>
<th>year</th>
<th>name</th>
<th>wins</th>
<th>losses</th>
</tr>
</thead>
<tbody>
<tr>
<td>...</td>
<td>&#x2192; franchises</td>
<td>1899</td>
<td>Cleveland Spiders</td>
<td>20</td>
<td>134</td>
</tr>
</tbody>
</table>
<p>One franchise has many team-seasons, so <code>franchise_id</code> goes on <code>teams</code>, the &quot;many&quot; side. The team&apos;s name stays on the season, because names change over time; the franchise is what stays the same. &quot;Which team has lost the most games in history?&quot; becomes a <code>SUM</code> of losses grouped by franchise instead of by name.</p>
<p>It isn&apos;t free, though. Someone has to decide which seasons belong to which franchise, and in Cleveland&apos;s case not all seven names are the same club (the 1899 Spiders were a different team from the one that became the Indians). The data can&apos;t make that call for you. That&apos;s what domain modeling actually is: decisions about the real world, written down as tables.</p>
<h3 id="naming-conventions">Naming conventions</h3>
<p>These are the conventions we&apos;ve been using, and we&apos;ll keep using them for the rest of the course. None of them is technically required, but following them makes everything easier to read, and it&apos;ll matter when we get to Rails.</p>
<ul>
<li>Models are singular and capitalized: Course, Section.</li>
<li>Table names are plural and lowercase: <code>courses</code>, <code>sections</code>.</li>
<li>Column names are lowercase: <code>name</code>, <code>description</code>.</li>
<li>Multi-word names use underscores: <code>first_name</code>, <code>teacher_id</code>.</li>
</ul>
<h3 id="next-week">Next week</h3>
<p>We picked the attributes in this post by saying what the product needs to do. Next week, we&apos;ll do that properly: start from user stories, and let the domain model come out of them.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SQL, optional: Aggregates and Grouping]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This one is optional. In class, <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>, <code>MIN</code>, <code>MAX</code> and <code>GROUP BY</code> were reading vocabulary: you need to recognize them when a chat writes them, not write them yourself. If you want to know how they work, here they are. It picks up where <a href="https://entr451.com/the-select-statement/">SQL 1</a> left off,</p></blockquote>]]></description><link>https://entr451.com/sql-aggregates-and-grouping/</link><guid isPermaLink="false">6abc0d1bd9019e19441c3321</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 19:11:24 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This one is optional. In class, <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>, <code>MIN</code>, <code>MAX</code> and <code>GROUP BY</code> were reading vocabulary: you need to recognize them when a chat writes them, not write them yourself. If you want to know how they work, here they are. It picks up where <a href="https://entr451.com/the-select-statement/">SQL 1</a> left off, with the same &#x1F476; <em>baby tables</em>.</p>
</blockquote>
<h3 id="aggregate-functions">Aggregate Functions</h3>
<p>Often, the questions you want answered will involve math. That is, instead of simply seeing a subset of the rows in a table, we&apos;ll want to <em>aggregate</em> the results together in some way, e.g. adding the number of rows, summing a single column, seeing an average, etc.</p>
<p><em>How many reviews have been written?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Reviews</code></li>
<li>Identify the columns that hold the information you need - not applicable in this case, since we&apos;re looking for an aggregate number</li>
<li>Add any other conditions - none</li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT COUNT(*) FROM Reviews;
</code></pre>
<table>
<thead>
<tr>
<th>COUNT(*)</th>
</tr>
</thead>
<tbody>
<tr>
<td>4</td>
</tr>
</tbody>
</table>
<p>There are a couple of crazy things going on here! Let&apos;s break it down.</p>
<p>First, instead of a <em>column name</em> like <em>Name, Price, Body</em>, etc. in our <code>SELECT</code> clause, we&apos;re calling a <strong>SQL function</strong>. There are many SQL functions built into SQL, but they will always contain a set of parentheses, which denote <em>arguments</em> to the function. In this case, we&apos;re using the <code>COUNT</code> function, which accepts an argument of &quot;what columns are we counting?&quot; Since we&apos;re simply looking for the aggregate number of rows in the table, the columns aren&apos;t relevant and we just use <code>*</code>.</p>
<p>And even though we&apos;re asking for an aggregate, that is, a single number representing the count of all the reviews, our result is still a &#x1F476; <em>baby table.</em> It simply contains a single column -- called <code>COUNT(*)</code> -- and a single row. We can call several SQL functions, or a combination of SQL functions and column names, which will result in a &#x1F476; <em>baby table</em> containing several columns*.*</p>
<p>Let&apos;s look at another aggregate function, where the argument does matter:</p>
<p><em>What is the average rating across all reviews?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Reviews</code></li>
<li>Identify the columns that hold the information you need - an average <code>Rating</code></li>
<li>Add any other conditions - none</li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT AVG(Rating) FROM Reviews;
</code></pre>
<table>
<thead>
<tr>
<th>AVG(Rating)</th>
</tr>
</thead>
<tbody>
<tr>
<td>4.25</td>
</tr>
</tbody>
</table>
<p>Similar as before, with our <code>COUNT</code> function, but this time, the argument matters. We&apos;re using the <code>AVG</code> function, which takes a column name as an argument - <em>which column are we averaging?</em></p>
<p>As mentioned, there are many functions built-in to SQL. The ones that are relevant to this lesson are <em>aggregate functions</em> - ones that do math to aggregate information. Examples of aggregate functions include:</p>
<ul>
<li><code>COUNT</code> - counts the number of rows</li>
<li><code>AVG</code> - average of a column value or expression</li>
<li><code>SUM</code> - sum of a column value or expression</li>
<li><code>MIN</code> - minimum of a column value or expression</li>
<li><code>MAX</code> - maximum of a column value or expression</li>
</ul>
<h3 id="grouping">Grouping</h3>
<p>Occasionally, a question like <em>How many reviews have been written?</em> will be enough to satisfy business requirements. However, it&apos;s more likely that your boss, client, or other stakeholder will want more specific information than simply aggregating the entire table&apos;s worth of data. We&apos;ll often want to categorize or aggregate (group) results by the value of another column. For example, a more common question might be:</p>
<p><em>How many reviews have been written per product?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Reviews</code></li>
<li>Identify the columns that hold the information you need - the <code>Product</code> and the aggregate number of reviews</li>
<li>Add any other conditions - none</li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT Product, COUNT(*) 
FROM Reviews
GROUP BY Product;
</code></pre>
<table>
<thead>
<tr>
<th>Product</th>
<th>COUNT(*)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>2</td>
</tr>
<tr>
<td>Sofa</td>
<td>1</td>
</tr>
<tr>
<td>Toaster</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>We see that the values separated by commas in the <code>SELECT</code> clause, whether they are column names or SQL functions, end up being the columns in the &#x1F476; <em>baby table</em>. And, because we used a <code>GROUP BY</code> clause, our results are aggregated (grouped) by the distinct values in the <code>Product</code> column.</p>
<p>This brings us to Rule #1A, the First Law of all SQL... <strong>when using a <code>GROUP BY</code> clause, the columns listed in the <code>GROUP BY</code> must also be in the <code>SELECT</code> clause.</strong> Makes sense, right? In this example, if the <code>Product</code> column wasn&apos;t also in the <code>SELECT</code>, the results would just be a list of numbers with no context:</p>
<pre><code class="language-sql">SELECT COUNT(*) 
FROM Reviews
GROUP BY Product;
</code></pre>
<table>
<thead>
<tr>
<th>COUNT(*)</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
</tr>
<tr>
<td>1</td>
</tr>
<tr>
<td>1</td>
</tr>
</tbody>
</table>
<p>Not very useful, is it?</p>
<p>The rule in the other direction is the one that actually bites: <strong>every column in the <code>SELECT</code> must either be in the <code>GROUP BY</code> or be inside an aggregate function.</strong> Most databases, PostgreSQL included, refuse to run a query that breaks it. SQLite runs it anyway, and quietly fills in a value from one row of each group. In the baseball database:</p>
<pre><code class="language-sql">SELECT year, name, MAX(wins)
FROM teams
WHERE year = 2002
GROUP BY year;
</code></pre>
<table>
<thead>
<tr>
<th>year</th>
<th>name</th>
<th>MAX(wins)</th>
</tr>
</thead>
<tbody>
<tr>
<td>2002</td>
<td>New York Yankees</td>
<td>103</td>
</tr>
</tbody>
</table>
<p>The Oakland Athletics also won 103 games in 2002. One row per group has room for one name, so SQLite picked one and didn&apos;t mention it. When a chat hands you SQL with a column like <code>name</code> sitting next to a <code>MAX</code> or a <code>COUNT</code>, check whether that column is in the <code>GROUP BY</code>. If it isn&apos;t, the name may not mean what it looks like it means.</p>
<h3 id="order-of-clauses">Order of Clauses</h3>
<p><a href="https://entr451.com/the-select-statement/">SQL 1</a> ended with the order of clauses; here it is again with <code>GROUP BY</code> in its place. Because of the way SQL is executed, the order in which the clauses are written does matter. This may differ slightly based on the flavor/implementation of SQL being worked with (i.e. some are more forgiving than others), but generally speaking, the order should be as follows:</p>
<ul>
<li><code>SELECT</code> - the columns or executed functions you want in the result</li>
<li><code>FROM</code> - the table(s) involved</li>
<li><code>WHERE</code> - the conditions or constraints</li>
<li><code>GROUP BY</code> - columns in the <code>SELECT</code> used to aggregate the results</li>
<li><code>ORDER BY</code> - columns by which the results shall be ordered, either numerically or alphabetically</li>
<li><code>LIMIT</code> - how many rows to return</li>
</ul>
<p>To put it all together:</p>
<p><em>What is the average rating per product, taking into account only those reviews with a rating of 3 or above, from lowest to highest average rating?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Reviews</code></li>
<li>Identify the columns that hold the information you need - the <code>Product</code> and the aggregate average rating of reviews</li>
<li>Add any other conditions - rating of 3 or above</li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT Product, AVG(Rating)
FROM Reviews
WHERE Rating &gt;= 3
GROUP BY Product
ORDER BY AVG(Rating);
</code></pre>
<table>
<thead>
<tr>
<th>Product</th>
<th>AVG(Rating)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Toaster</td>
<td>3</td>
</tr>
<tr>
<td>Camera</td>
<td>4.5</td>
</tr>
<tr>
<td>Sofa</td>
<td>5</td>
</tr>
</tbody>
</table>
<h3 id="try-it-aggregation">Try it: aggregation</h3>
<p>In class, aggregates were reading vocabulary: you will see them in SQL a chat writes for you far more often than you will write them. Reading is easier once you have written one or two, so:</p>
<ul>
<li><em>How many teams played in 2020?</em> One <code>COUNT(*)</code> and one <code>WHERE</code>. (30.)</li>
<li><em>Which team name has been used for the most seasons?</em> <code>GROUP BY name</code>, <code>COUNT(*)</code>, sorted, and one row. Then ask for three rows, and see what one row was hiding.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[SQL 2: Table Relationships]]></title><description><![CDATA[<p>In the <a href="https://entr451.com/the-select-statement/">last lesson</a>, we learned the basics of SQL and are well on our way to knowing just about everything we&apos;d want to know about how to perform query operations on a single table. But of course, a database is a collection of multiple tables, with each</p>]]></description><link>https://entr451.com/table-relationships/</link><guid isPermaLink="false">6abc0b17d9019e19441c32ff</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 19:08:53 GMT</pubDate><content:encoded><![CDATA[<p>In the <a href="https://entr451.com/the-select-statement/">last lesson</a>, we learned the basics of SQL and are well on our way to knowing just about everything we&apos;d want to know about how to perform query operations on a single table. But of course, a database is a collection of multiple tables, with each table storing a different dimension of information. In our e-commerce example, we have a table for <em>Products</em> and another for <em>Reviews</em>. Why? We&apos;ll get deeper into this in an upcoming lesson on <em>Domain Modeling </em>(which just a fancy way of saying &quot;how to design tables&quot;) &#x2013;&#xA0;but, given the existing design, how do we ask questions of multiple tables? In this lesson, we&apos;ll take a deep dive into doing just that.</p><h3 id="joins">JOINs</h3><p>Sometimes the answer to a question is contained within more than one table. Consider our <code>Products</code> table:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>$299</td>
<td>Electronics</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
<td>Furniture</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
<td>Furniture</td>
</tr>
<tr>
<td>Toaster</td>
<td>$79</td>
<td>Housewares</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>And <code>Reviews</code>:</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th>Product</th>
<th>Rating</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>4</td>
<td>Takes nice pictures!</td>
</tr>
<tr>
<td>Sofa</td>
<td>5</td>
<td>Comfy!</td>
</tr>
<tr>
<td>Camera</td>
<td>5</td>
<td>Best camera ever!!!</td>
</tr>
<tr>
<td>Toaster</td>
<td>3</td>
<td>Makes toast.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>And the question we want answered is:</p><p><em>Can I see all the reviews for the products in the Furniture department?</em></p><p>The results &#x2013; our &#x1F476; <em>baby table</em> &#x2013; and the <code>WHERE</code> clause would both need to contain columns from both the <code>Products</code> and <code>Reviews</code> tables. Let&apos;s follow our usual formula and see what happens:</p><ul><li>Identify the table(s) that contain the information you need &#x2013; <code>Reviews</code> and <code>Products</code></li><li>Identify the columns the hold the information you need &#x2013;&#xA0;the <code>Product</code>, <code>Rating</code>, and <code>Body</code> from <code>Reviews</code>, as well as <code>Department</code> from <code>Products</code></li><li>Add any other conditions &#x2013;&#xA0;products in the <em>Furniture</em> department</li></ul><p>Before being able to ask the question, though, we need to add one additional step to this recipe.</p><ul><li>Identify the thing the tables have in common&#xA0;&#x2013;&#xA0;the <code>Name</code> from the <code>Products</code> table is equivalent to the <code>Product</code> column from the <code>Reviews</code> table</li></ul><p>Now, we can ask the question:</p><pre><code>SELECT Products.Department, Reviews.Product, Reviews.Rating, Reviews.Body
FROM Reviews INNER JOIN Products ON Products.Name = Reviews.Product
WHERE Products.Department = &quot;Furniture&quot;;</code></pre><p>Whew! There seems to be a lot more here than what we&apos;ve seen thus far... but don&apos;t worry; it&apos;s actually quite straightforward. Let&apos;s break it down.</p><p>Because there are multiple tables involved, we&apos;re prefixing each column name with the name of the table and a dot <code>.</code> &#x2013;&#xA0;e.g. because the <code>Body</code> column is in the <code>Reviews</code> table, we write it as <code>Reviews.Body</code>. This is prevent conflicts in naming &#x2013; for instance, what if the <code>Products</code> and <code>Reviews</code> tables both had a <code>Body</code> column? This syntax clears that up.</p><p>And, the <code>FROM</code> clause is more complex now. Previously, it was just the name of the single table we were reading from. Now, we&apos;re reading from the intersection of the two tables. This intersection is called a <code>JOIN</code>, and the commonality between the two tables is specified in the <code>ON</code> clause. The intersection between the table happens when the <code>Name</code> from the <code>Products</code> table is equivalent to the <code>Product</code> column from the <code>Reviews</code> table, so it&apos;s <code>ON Products.Name = Reviews.Product</code>. Furthermore, if there was a third, fourth, fifth, etc. table involved, they would be chained together in the <code>FROM</code> clause with more <code>INNER JOIN</code>s.</p><h3 id="the-primary-key">The Primary Key</h3><p>A shocking confession: the examples I&apos;ve provided thus far &#x2013; the <code>Products</code> and <code>Reviews</code> tables from our fictional e-commerce store that sells a rather eclectic variety of stuff &#x2013;&#xA0;hasn&apos;t been entirely realistic. The one thing that basically all tables in a relational database have, and that&apos;s missing from these tables, is some sort of unique identifier for each record. A <em><strong>primary key</strong></em>. If you&apos;ve ever seen a database table outside of this course, you may have seen a variety of different types of primary keys. Based on the database design, sometimes you&apos;ll see something like a Social Security Number, or some other strictly-enforced integer value. Sometimes it&apos;s a long alphanumeric string based on a software algorithm. Or, a lot of the time, it&apos;s simply <strong>an integer that starts at 1 and goes up by 1, forever</strong>. The <em>auto-incrementing integer</em> primary key is fairly common for a freshly-built application nowadays, and that will be the rule for primary keys that we&apos;ll follow for the reminder of this course.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Hey guys, wanna feel old?<br><br>I&apos;m 40.<br><br>You&apos;re welcome.</p>&#x2014; Macaulay Culkin (@IncredibleCulk) <a href="https://twitter.com/IncredibleCulk/status/1298730289737293824?ref_src=twsrc%5Etfw">August 26, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<figcaption>https://twitter.com/IncredibleCulk/status/1298730289737293824</figcaption></figure><p>Have a look at the permanent URL for the tweet above &#x2013;&#xA0;that URL never changes, and will always direct you to the page for that tweet, unless Mr. Culkin decides to delete it. What is the <em>1298730289737293824 </em>all about? That&apos;s the primary key. That&apos;s right, there was a tweet #1 at some point, and at the time this tweet was posted, it was the <em>1,298,730,289,737,293,824th</em> tweet that had ever been posted. The number never goes down, and the number never gets reused. Luckily, there are a lot of numbers in the universe still left to use.</p><h3 id="foreign-keys">Foreign Keys</h3><p>Now that we know <em>what</em> a primary key is, let&apos;s look at <em>why</em> it&apos;s so important to use them in database design. First and foremost, we use primary keys to make our data design less fragile. To see what I mean by that &#x2013; let&apos;s look at a quick example. If we look at the existing table design we&apos;ve been working with, which does not contain primary keys, we can see how we can easily break the integrity of the data. For instance, what if we change the name of the &quot;camera&quot; product to something else?</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td><s>Camera</s> <strong>Digital SLR Camera</strong></td>
<td>$299</td>
<td>Electronics</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
<td>Furniture</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
<td>Furniture</td>
</tr>
<tr>
<td>Toaster</td>
<td>$79</td>
<td>Housewares</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><table>
<thead>
<tr>
<th>Product</th>
<th>Rating</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>4</td>
<td>Takes nice pictures!</td>
</tr>
<tr>
<td>Sofa</td>
<td>5</td>
<td>Comfy!</td>
</tr>
<tr>
<td>Camera</td>
<td>5</td>
<td>Best camera ever!!!</td>
</tr>
<tr>
<td>Toaster</td>
<td>3</td>
<td>Makes toast.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>Anarchy! We&apos;ve immediately broken the relationship between the <code>Products</code> and <code>Reviews</code> tables. Now, the rows in <code>Reviews</code> that previously referenced the &quot;Camera&quot; are now <em>orphaned, </em>that is, they are left with no valid parent record that is referenced in its <code>Product</code> column. The SQL query that we just wrote to get product reviews in the furniture department &#x2013; broken. Of course, we could update each review to reference the new name of the product. That would be easy enough in a database of this size, but certainly not in a database of more complex design and possibly millions of records. It&apos;s much better to update our database design to include a primary key.</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Digital SLR Camera</td>
<td>$299</td>
<td>Electronics</td>
</tr>
<tr>
<td>2</td>
<td>Sofa</td>
<td>$699</td>
<td>Furniture</td>
</tr>
<tr>
<td>3</td>
<td>Dining Table</td>
<td>$1299</td>
<td>Furniture</td>
</tr>
<tr>
<td>4</td>
<td>Toaster</td>
<td>$79</td>
<td>Housewares</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>The auto-incrementing primary key <code>ID</code> column starts at 1 and simply increments by 1 for each new record in our <code>Products</code> table. Next, we slightly re-design the <code>Reviews</code> table to point to the product&apos;s <code>ID</code> instead of its <code>Name</code>:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>ID</th>
<th>ProductID</th>
<th>Rating</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>4</td>
<td>Takes nice pictures!</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>5</td>
<td>Comfy!</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>5</td>
<td>Best camera ever!!!</td>
</tr>
<tr>
<td>4</td>
<td>3</td>
<td>3</td>
<td>Makes toast.</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>And to fix our SQL from above, we join by the <code>ID</code> and <code>ProductID</code>, instead of the <code>Name</code> of the product:</p><pre><code>SELECT Products.Department, Reviews.Product, Reviews.Rating, Reviews.Body
FROM Reviews INNER JOIN Products ON Products.ID = Reviews.ProductID
WHERE Products.Department = &quot;Furniture&quot;;</code></pre><p>The <code>ProductID</code> in the <code>Reviews</code> column is known as the <strong>foreign key</strong>. A foreign key is simply a column in a table that points to a primary key in another table.</p><h3 id="try-it-the-baseball-database"><strong>Try it: the baseball database</strong></h3><p>The baseball database is built exactly this way. <code>players</code> and <code>teams</code> each have an <code>id</code>, and <code>stats</code> holds one row per player per team per season, pointing at both with two foreign keys, <code>player_id</code> and <code>team_id</code>. To ask anything about a player&apos;s season, you join through it:</p><!--kg-card-begin: markdown--><pre><code class="language-sql">SELECT players.first_name, players.last_name, teams.year, teams.name, stats.home_runs
FROM stats
INNER JOIN players ON players.id = stats.player_id
INNER JOIN teams ON teams.id = stats.team_id
WHERE players.last_name = &apos;Griffey&apos;;
</code></pre>
<!--kg-card-end: markdown--><p>Run it on the <strong><strong>Run SQL</strong></strong> page, and look at the <code>id</code> of each player in the results (add <code>players.id</code> to the <code>SELECT</code> to see it). This is the Ken Griffey question from class: two players, one name. A query that adds up his home runs by name answers a question nobody asked. A query that uses his <code>id</code> cannot make that mistake, which is the whole argument of this post in one line.</p>]]></content:encoded></item><item><title><![CDATA[SQL 1: Intro to SQL and the SELECT Statement]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>SQL stands for <em>Structured Query Language</em> - see, you&apos;re halfway through the first sentence of this course, and you&apos;re already half asleep! Simply put, SQL is a way for us humans to communicate with databases. And &quot;database&quot; is a pretty generic term for &quot;</p>]]></description><link>https://entr451.com/the-select-statement/</link><guid isPermaLink="false">6abc0ab4d9019e19441c32f0</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 19:01:13 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>SQL stands for <em>Structured Query Language</em> - see, you&apos;re halfway through the first sentence of this course, and you&apos;re already half asleep! Simply put, SQL is a way for us humans to communicate with databases. And &quot;database&quot; is a pretty generic term for &quot;way for a computer to store data&quot;. There are lots of ways for a computer to store data, including (but certainly not limited to):</p>
<ul>
<li>CSV, JSON, or other structured text file</li>
<li>Spreadsheet</li>
<li>Document database</li>
<li>Relational database management system (RDBMS)</li>
</ul>
<p>That last one -- relational databases or RDBMS -- is the one we&apos;re going to concentrate on in this course. While there are many software applications that store data in other ways, including those listed, a vast majority of business-class software applications use some flavor of relational database to store data. These &quot;flavors&quot; are vast - some are open-source solutions, and some are proprietary and commercial. Examples of these are:</p>
<ul>
<li>MySQL (open-source)</li>
<li>PostgreSQL (open-source)</li>
<li>SQLite (open-source)</li>
<li>IBM DB2 (closed-source, commercial)</li>
<li>Microsoft SQL Server (closed-source, commercial)</li>
<li>Oracle Database (closed-source, commercial)</li>
</ul>
<p>And, although there are a large number of these flavors, developed by many different companies and organizations, the core of the SQL language is a standard that may be used to communicate with any of them.</p>
<p>Let&apos;s dive right in.</p>
<h2 id="the-basics">The Basics</h2>
<p>Relational databases store data in tabular format. If you&apos;ve ever used Microsoft Excel, Google Sheets, or any other spreadsheet application to store a list of anything, you&apos;ve probably invented a way to organize data that is very similar to how databases do it - perhaps:</p>
<ul>
<li>Your data is stored in a grid format</li>
<li>The columns of the grid represent the <em>attributes</em> of the thing you are storing</li>
<li>There is a <em>row</em> in the grid for each one of the things, and maybe that&apos;s called a <em>record</em></li>
</ul>
<p>For example, if I were to build a spreadsheet of my favorite movies, it might look something like this:</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Year</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Star Wars</td>
<td>1977</td>
<td>PG</td>
</tr>
<tr>
<td>2</td>
<td>Apollo 13</td>
<td>2004</td>
<td>PG</td>
</tr>
<tr>
<td>3</td>
<td>The Matrix</td>
<td>2007</td>
<td>R</td>
</tr>
</tbody>
</table>
<p>Pretty straightforward. And, if I want to add more attributes to my data -- say, I want to add the studio that produced the movie -- I&apos;d simply add to the columns of data.</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Year</th>
<th>Rating</th>
<th>Studio</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Star Wars</td>
<td>1977</td>
<td>PG</td>
<td>Lucasfilm</td>
</tr>
<tr>
<td>2</td>
<td>Apollo 13</td>
<td>2004</td>
<td>PG</td>
<td>Universal</td>
</tr>
<tr>
<td>3</td>
<td>The Matrix</td>
<td>2007</td>
<td>R</td>
<td>Warner Bros.</td>
</tr>
</tbody>
</table>
<p>And, if I want to add more movies, I&apos;d add to the rows of data.</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Year</th>
<th>Rating</th>
<th>Studio</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Star Wars</td>
<td>1977</td>
<td>PG</td>
<td>Lucasfilm</td>
</tr>
<tr>
<td>2</td>
<td>Apollo 13</td>
<td>2004</td>
<td>PG</td>
<td>Universal</td>
</tr>
<tr>
<td>3</td>
<td>The Matrix</td>
<td>2007</td>
<td>R</td>
<td>Warner Bros.</td>
</tr>
<tr>
<td>4</td>
<td>Forrest Gump</td>
<td>1994</td>
<td>PG-13</td>
<td>Paramount</td>
</tr>
</tbody>
</table>
<p>As simple as this example is, this is precisely how data is structured in a relational database system. To offer up some definitions - the columns are known as the <em>fields</em> or <em>attributes</em>, the rows are known as <em>records</em>, and the rows+columns together is a <em>table.</em> When we have a collection of many tables, that&apos;s a <em>database.</em></p>
<p>In practice, the main difference between a spreadsheet (e.g. Excel) and a database system is that Excel already includes a standard front-end by which an end-user (like you) is able to read and write to the data. Since the use-cases for RDBMSes are much more varied and programmable than a spreadsheet, there&apos;s no such standard for relational databases. Instead, we use SQL to talk with the database, and to pass the results on to our front-end of choice, whether that&apos;s a custom web application, a PDF report, a commercial front-end like Tableau, or whatever else might be needed.</p>
<h2 id="using-sql-to-read-data">Using SQL to read data</h2>
<p>In this lesson, we&apos;ll be looking at ways we can use SQL to <strong>read</strong> data from a relational database. In a later lesson, we will be looking at using SQL to <strong>write</strong> to and <strong>modify</strong> a database.</p>
<h3 id="the-select-statement">The SELECT statement</h3>
<p>We read data from a database by using the <code>SELECT</code> statement in SQL. Let&apos;s look at an example. Suppose we own an e-commerce store - we&apos;d probably have a a database table used to store the products we sell. This table would likely be named <code>Products</code> and look something like this:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>$299</td>
<td>Electronics</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
<td>Furniture</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
<td>Furniture</td>
</tr>
<tr>
<td>Toaster</td>
<td>$79</td>
<td>Housewares</td>
</tr>
</tbody>
</table>
<p>The <code>SELECT</code> statement allows us to ask the database questions. <em>What products do we sell? How much does the Toaster cost? How many products cost more than $500?</em></p>
<p>It would be nice if the SQL language knew how to answer your question, in exactly the way you asked it. But, no, it&apos;s a computer, so we have to follow a simple formula so that you can ask the question in a way that SQL can understand:</p>
<ul>
<li>Identify the table that holds the information you need</li>
<li>Identify the columns that hold the information you need</li>
<li>Add any other conditions</li>
<li>Then, ask the question in SQL</li>
</ul>
<p>For instance, if my question is: <em>what are the names of the products we sell?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code></li>
<li>Add any other conditions - none</li>
<li>Then, ask the question:</li>
</ul>
<p><code>SELECT Name FROM Products;</code></p>
<p>There are 3 things about this <code>SELECT</code> statement that I&apos;ll point out:</p>
<ul>
<li>It ends with a semicolon <code>;</code> - this depends on the flavor of RDBMS and the front-end being used, but many databases will require the semicolon at the end of the SQL statement, so all the examples we&apos;ll use will include it</li>
<li>The column and table names are case-sensitive; again, this will depend on the specific database system, but for illustration&apos;s sake, all examples will match the table/column name casing in the database</li>
<li>The <code>SELECT</code> and <code>FROM</code> <em>clauses</em> are in ALL CAPS; this is NOT a requirement in SQL, but <em>clauses</em> in examples will be written this way, as it is often more readable</li>
</ul>
<p>The result that we&apos;ll receive is actually another, temporary table - i.e. one that is not part of the database structure or <em>schema</em> as-designed, but is simply a temporary subset of the larger table we&apos;ve targeted. I like to think of this as a &quot;&#x1F476;baby table&quot; - a much smaller table that looks a lot like the larger, original table, but only holds the data we asked for:</p>
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
</tr>
<tr>
<td>Sofa</td>
</tr>
<tr>
<td>Dining Table</td>
</tr>
<tr>
<td>Toaster</td>
</tr>
</tbody>
</table>
<p><em>To be clear, &quot;&#x1F476; <strong>baby table</strong>&quot; is not an official SQL term... it&apos;s just what I like to call it.</em></p>
<p>Now, it&apos;s up to you to decide if the results provided in this &quot;&#x1F476; baby table&quot; are everything you need, or if you need to modify your question in order to get more information.</p>
<p><em>Hmmm, it would nice if I could see the price too. Can I get a list of all the products we sell, along with the price?</em></p>
<p>Back to the formula:</p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code></li>
<li>Add any other conditions - none</li>
<li>Then, ask the question:</li>
</ul>
<p><code>SELECT Name, Price FROM Products;</code></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>$299</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
</tr>
<tr>
<td>Toaster</td>
<td>$79</td>
</tr>
</tbody>
</table>
<h3 id="sorting">Sorting</h3>
<p>Now that we&apos;ve seen how to use the <code>SELECT</code> statement in SQL to retrieve rows of data from a database table, let&apos;s take one step toward doing something more meaningful. It&apos;s pretty rare that we&apos;ll want to get all records in a database table, in exactly the order that the records were inserted - this is what we&apos;ve done thus far.</p>
<p>We&apos;ll often want our records in our &#x1F476; <em>baby table</em> to be in a specific order - perhaps alphabetically or sorted by a numeric value. This is going to be based on how the data is ultimately consumed - e.g. in a PDF report for a stakeholder in your company, displayed on-screen for a user of a web application, and so on. Let&apos;s continue with our question and answer format for this one:</p>
<p><em>Can we order the results by price?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code></li>
<li>Add any other conditions - sort by <code>Price</code></li>
<li>Then, ask the question:</li>
</ul>
<p><code>SELECT Name, Price FROM Products ORDER BY Price;</code></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Toaster</td>
<td>$79</td>
</tr>
<tr>
<td>Camera</td>
<td>$299</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
</tr>
</tbody>
</table>
<p>As we can see, ascending order (from the least to the greatest) is the default when sorting a numeric value. (It&apos;s alphabetically from A-Z with a non-numeric value.)</p>
<p><em>Can we order by the more expensive products first?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code></li>
<li>Add any other conditions - sort by <code>Price</code> in descending order</li>
<li>Then, ask the question:</li>
</ul>
<p><code>SELECT Name, Price FROM Products ORDER BY Price DESC;</code></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dining Table</td>
<td>$1299</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
</tr>
<tr>
<td>Camera</td>
<td>$299</td>
</tr>
<tr>
<td>Toaster</td>
<td>$79</td>
</tr>
</tbody>
</table>
<h3 id="limiting">Limiting</h3>
<p>Another common use-case is that we want to limit the number of rows in our &#x1F476;<em>baby table</em> - what we&apos;ve done so far is just fine for our very small table with 4 rows of data. But what if our <code>Products</code> table contained 10 million rows? The <code>SELECT</code> statements we&apos;ve written thus far would be very slow, and not likely to be very helpful or meaningful to the person receiving the results. So we must often reduce our results, and that usually means refining the question we&apos;re asking.</p>
<p><em>What is the single most expensive product we sell?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code></li>
<li>Add any other conditions - sort by <code>Price</code> in descending order and only return 1 row</li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT Name, Price
FROM Products
ORDER BY Price DESC
LIMIT 1;
</code></pre>
<p>Same results as before - but only returns a single record, answering our question. Also note that we&apos;re separating each fragment of the SQL statement here -- AKA <em>clauses</em> -- by putting each on its own line. As our SQL becomes more complex, I&apos;ve found this easier to read and comprehend -- especially when first learning -- so we&apos;ll do this from now on.</p>
<h3 id="try-it-the-baseball-database">Try it: the baseball database</h3>
<p>Let&apos;s play ball! Open your copy of the <code>baseball</code> repository in its Codespace (the one you made in class; reopen it from <a href="https://github.com/codespaces">github.com/codespaces</a> rather than making a new one), run <code>bin/rails server</code> in the terminal, and open the port it offers. The app opens on the <strong>Run SQL</strong> page, a box that runs whatever SQL you type into it. The database has three tables:</p>
<pre><code>teams (id, year, name, park, wins, losses)
players (id, first_name, last_name, bats, throws)
stats (id, team_id, player_id, games, at_bats, runs, hits, doubles, triples, home_runs, rbis)
</code></pre>
<p>Try it:</p>
<p><code>SELECT first_name, last_name FROM players;</code></p>
<p><strong>Don&apos;t forget that semi-colon!</strong> You should see the first and last names of all 20,358 players in the database. Then try the question from class, the five winningest seasons ever, using the formula above: which table, which columns, sorted how, how many rows?</p>
<p>If you would rather work in the terminal, save a query in a file in <code>queries/</code> and run it with <code>bin/query queries/your-file.sql</code>. And if you want to see the database itself, with no page in front of it, <code>bin/rails db</code> opens SQLite&apos;s own prompt (type <code>.quit</code> to leave). We will live there in week 5.</p>
<h3 id="filtering-data">Filtering Data</h3>
<p>The ability to filter data is probably the most powerful and often-used feature of SQL. For many people, gaining insights from arbitrary sets of data is often the objective of learning SQL in the first place.</p>
<p>We&apos;ve already learned about the <code>LIMIT</code> clause, which simply whittles down the size of our result set -- the &#x1F476; <em>baby table</em> -- by some number that we provide. However, we often want to specify criteria for filtering the results, rather than a set number like <code>10</code>. <em>What are our best-selling products? Which sales tactics are producing the best results this month? How many orders did we receive for the new widget this week?</em></p>
<p>For questions like these, we must use the <code>WHERE</code> clause. The <code>WHERE</code> clause allows us to add more criteria to the questions we ask. Let&apos;s jump into an example using our <em>Products</em> table:</p>
<p><em>Which products cost more than $500?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code></li>
<li>Add any other conditions - <code>Price</code> is more than $500</li>
<li>Then, ask the question:</li>
</ul>
<p><code>SELECT Name, Price FROM Products WHERE Price &gt; 500;</code></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dining Table</td>
<td>$1299</td>
</tr>
<tr>
<td>Sofa</td>
<td>$699</td>
</tr>
</tbody>
</table>
<p>A <code>WHERE</code> clause is going to always have a column name (in this case, <code>Price</code>), a comparison operator (<code>&gt;</code> or &quot;greater than&quot;) and a value to compare (<code>500</code>). Because the column in question contains numbers, it&apos;s going to compare the <code>Price</code> of every row in the table against the numeric value <code>500</code> and filter the results to only include those with a price greater than that number. Let&apos;s look at what happens with a non-numeric comparison:</p>
<p><em>Which products are sold in the Furniture department?</em></p>
<ul>
<li>Identify the table that holds the information you need - <code>Products</code></li>
<li>Identify the columns that hold the information you need - <code>Name</code> and <code>Price</code> again, but we probably want <code>Department</code> now too</li>
<li>Add any other conditions - <code>Department</code> is <em>Furniture</em></li>
<li>Then, ask the question:</li>
</ul>
<pre><code class="language-sql">SELECT Name, Price, Department 
FROM Products 
WHERE Department = &quot;Furniture&quot;;
</code></pre>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sofa</td>
<td>$699</td>
<td>Furniture</td>
</tr>
<tr>
<td>Dining Table</td>
<td>$1299</td>
<td>Furniture</td>
</tr>
</tbody>
</table>
<p>Again, we have a column name (in this case, <code>Department</code>), a comparison operator (<code>=</code> or &quot;exactly equals&quot;) and a value to compare (<code>&quot;Furniture&quot;</code>). Because <code>Department</code> is non-numeric, the value to compare <strong>must be in quotes</strong>. This is necessary to distinguish the comparison value from columns name, other SQL clauses, variables, and the like.</p>
<p>Separate multiple filters/conditions with the <code>AND</code> clause. So if we wanted the answer to:</p>
<p><em>Which products are sold in the furniture department and cost more than $500?</em></p>
<pre><code class="language-sql">SELECT Name, Price, Department 
FROM Products 
WHERE Department = &quot;Furniture&quot;
  AND Price &gt; 500;
</code></pre>
<h3 id="matching-a-pattern">Matching a pattern</h3>
<p><code>=</code> means <em>exactly equal</em>, and sometimes that is not what you want. <code>LIKE</code> matches a pattern, where <code>%</code> means &quot;anything, or nothing, here.&quot; In the baseball database:</p>
<pre><code class="language-sql">SELECT year, name, park
FROM teams
WHERE park = &apos;Wrigley Field&apos;;
</code></pre>
<p>returns 107 rows, every season played at the ballpark in Chicago. Change the last line to:</p>
<pre><code class="language-sql">WHERE park LIKE &apos;%Wrigley%&apos;;
</code></pre>
<p>and you get 108. The extra one is the 1961 Los Angeles Angels, who played their first season at a different ballpark, <code>Wrigley Field (LA)</code>. Neither query is wrong; they answer different questions. The question you asked and the question you typed are not always the same question, and the only way to know which one you got is to read the <code>WHERE</code>.</p>
<h3 id="try-it-filtering">Try it: filtering</h3>
<p>On the <strong>Run SQL</strong> page:</p>
<ul>
<li><em>Every team that has played at Wrigley Field.</em> Then change <code>=</code> to <code>LIKE</code> and find the extra row.</li>
<li><em>Which team won the most games in 2002?</em> You will probably write <code>ORDER BY wins DESC LIMIT 1</code>. Then change the <code>1</code> to a <code>2</code>. Be honest with yourself about whether you would have checked.</li>
</ul>
<h3 id="a-star-is-born">A Star is Born</h3>
<p>We&apos;ve already seen how SQL queries require us to provide the columns that we want to be returned in the &#x1F476; <em>baby table</em> - and how we often need to think about what columns those are when providing clean, concise output. Occasionally though, the columns don&apos;t really matter. In these cases, we can use the asterisk or star (<code>*</code>) to denote that we want <em>all columns</em> in a table. Consider a <code>Reviews</code> table, which contains reviews about our products. If we simply want <em>all columns, all rows</em> from this table, we could write a query that looks like:</p>
<p><code>SELECT * from reviews;</code></p>
<p>And the result would be everything -- all columns and all rows -- from that table.</p>
<table>
<thead>
<tr>
<th>Product</th>
<th>Rating</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td>Camera</td>
<td>4</td>
<td>Takes nice pictures!</td>
</tr>
<tr>
<td>Sofa</td>
<td>5</td>
<td>Comfy!</td>
</tr>
<tr>
<td>Camera</td>
<td>5</td>
<td>Best camera ever!!!</td>
</tr>
<tr>
<td>Toaster</td>
<td>3</td>
<td>Makes toast.</td>
</tr>
</tbody>
</table>
<p>In practice, we&apos;re rarely going to use it like this, unless a table contains just a few columns, or when we don&apos;t care about concise output, like when we&apos;re testing our queries. Or, within an aggregate function, which is where the <a href="https://entr451.com/sql-aggregates-and-grouping/">optional next post</a> picks up.</p>
<h3 id="order-of-clauses">Order of Clauses</h3>
<p>We&apos;ve seen enough clauses now to talk about the order they go in. Because of the way SQL is executed, the order in which the clauses are written does matter:</p>
<ul>
<li><code>SELECT</code> - the columns you want in the result</li>
<li><code>FROM</code> - the table(s) involved</li>
<li><code>WHERE</code> - the conditions or constraints</li>
<li><code>ORDER BY</code> - columns by which the results shall be ordered, either numerically or alphabetically</li>
<li><code>LIMIT</code> - how many rows to return</li>
</ul>
<p>To put it all together, in the baseball database:</p>
<p><em>Which three teams won the most games in the 2000s?</em></p>
<pre><code class="language-sql">SELECT year, name, wins
FROM teams
WHERE year &gt;= 2000 AND year &lt; 2010
ORDER BY wins DESC
LIMIT 3;
</code></pre>
<p>Run it, then change the <code>3</code> to a <code>4</code>. The 2002 Yankees are third with 103 wins. The 2002 Oakland Athletics also won 103, and they&apos;re fourth only because of the order the rows happen to sit in. <code>LIMIT</code> cuts where you tell it to, whether or not that&apos;s a sensible place to cut.</p>
<h3 id="going-further-optional">Going further (optional)</h3>
<p>That&apos;s everything we covered in class, and everything you need to read the SQL a chat writes for you in this course. If you&apos;re curious about the math side of SQL, counting, averaging and grouping, it&apos;s in <a href="https://entr451.com/sql-aggregates-and-grouping/">the optional next post</a>. Otherwise, on to <a href="https://entr451.com/table-relationships/">SQL 2: Table Relationships</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Judging the analyst's answer]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is the longer version of the exercise at the center of Week 2: asking a chat about a database, reading the SQL it writes, and deciding whether to believe the number. If the loop went by too fast in class, or you want the reasoning behind it, start here.</p></blockquote>]]></description><link>https://entr451.com/judging-the-analysts-answer/</link><guid isPermaLink="false">6abc0870d9019e19441c32ba</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 18:55:03 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is the longer version of the exercise at the center of Week 2: asking a chat about a database, reading the SQL it writes, and deciding whether to believe the number. If the loop went by too fast in class, or you want the reasoning behind it, start here. The SQL we covered (SELECT, WHERE, ORDER BY, LIMIT, joins, keys) is in <a href="https://entr451.com/the-select-statement/">SQL 1</a> and <a href="https://entr451.com/table-relationships/">SQL 2</a>.</p>
</blockquote>
<h2 id="you-were-the-stakeholder">You were the stakeholder</h2>
<p>Every company that runs on data has the same three kinds of people touching it. Analysts read data and turn it into decisions. Developers build the applications that read and write it. Database administrators change its structure. And then there is everyone else, which is most people, and probably you: the stakeholder who has a question, hands it to someone who can answer it, and gets a number back.</p>
<p>In class, the chat was the analyst and you were the stakeholder. You did not write the SQL. Your job was harder: decide whether to believe it.</p>
<p>That is the job this course keeps giving you, and it gets more important, not less, as the tools get better. An analyst who is right nine times out of ten is extremely useful, as long as somebody can tell which time is the tenth.</p>
<h2 id="the-analyst-knows-only-what-you-give-it">The analyst knows only what you give it</h2>
<p>Before the first question, you pasted a brief into your chat. It said: this is a SQLite database, these are the tables and their columns, the seasons run from 1871 to 2020, and when I ask a question, give me the SQL and the answer you expect it to return.</p>
<p>That brief is the whole of what the chat knew about your database. It never saw the file. It could not look at a row. Everything it got right came from what you gave it, and everything it got wrong came from what you did not.</p>
<p>If you skipped the brief, you saw what happens instead: the chat invents a schema. It writes a confident query against a <code>franchise</code> column or an <code>HR</code> column that does not exist, and the query fails. That is the cheap version of the lesson. The expensive version is a question with just enough context to run and not enough to be right.</p>
<p>The word for what you pasted is <strong>context</strong>, and it will come back all quarter.</p>
<h2 id="steps">Steps</h2>
<ol>
<li><strong>Ask.</strong> Paste the question exactly as written.</li>
<li><strong>Read.</strong> What comes back is two things: the SQL, and the number the chat expects it to return.</li>
<li><strong>Run.</strong> Paste the SQL into the <strong>Run SQL</strong> page yourself, and compare what the database returns with what the chat predicted.</li>
<li><strong>Save.</strong> Keep the query in <code>queries/</code>, with the question on the first line, so the <strong>Queries</strong> page shows it next to the others.</li>
<li><strong>Decide.</strong> Write one sentence: trust, or don&apos;t trust, because.</li>
</ol>
<p>The &quot;because&quot; is the skill. &quot;Trust, because the SQL filters on the right year and 107 wins is a believable season&quot; is a judgment. &quot;Trust, because it looked right&quot; is a coin flip with extra steps.</p>
<h2 id="the-number-was-memory-the-sql-was-work">The number was memory. The SQL was work.</h2>
<p>Asking for both the SQL and the expected answer was deliberate, because they come from different places.</p>
<p>The chat has read an enormous amount about baseball. Ask it who won the most games in 2019 and it will tell you the Houston Astros, 107, before it has written a line of SQL, because it remembers. That expected answer is a prediction from memory. It is often right. It is not evidence.</p>
<p>The SQL is the only part of the reply that touches your data. When you run it, the database answers the question the SQL asks, which is not always the question you asked.</p>
<p>On the first question, the two agreed: the query returned the Astros with 107, and the chat had said so. That agreement is what trust looks like. On the Ken Griffey question, some of you saw them disagree: the chat said 630 home runs, and its own SQL returned 782. Only one of those numbers came from the data, and it was not the one it was confident about.</p>
<h2 id="three-ways-a-correct-looking-answer-goes-wrong">Three ways a correct-looking answer goes wrong</h2>
<p>None of these produce an error. The query runs, a number appears, and nothing on the screen tells you anything is wrong.</p>
<h3 id="the-question-meant-more-than-one-thing">The question meant more than one thing</h3>
<p>&quot;Which team has lost the most games in history?&quot; In this database there are at least three answers:</p>
<ul>
<li>The worst single season: the 1899 Cleveland Spiders, 20 wins and 134 losses.</li>
<li>The most losses added up by team name: the Philadelphia Phillies, 10,426.</li>
<li>The most losses by franchise, which is what a fan means: <strong>unanswerable</strong>. The database has no idea that the Cleveland Bronchos, Naps and Indians are one club, or that the Cleveland Spiders were a different one. It has team names per season, and Cleveland alone appears under seven of them.</li>
</ul>
<p>The chat had to pick one, and most of them picked without telling you. Some asked which one you meant, or gave you both, which is what a good analyst does. Either way, the ambiguity was in the question before it was in the SQL. If you do not say which answer you want, someone else decides.</p>
<p>And when none of the readings is the one you meant, that is not a SQL problem. It is a data model problem: the database is missing something (here, a franchise) that the question assumes. Which is where the second half of the class went.</p>
<h3 id="the-query-matched-on-a-name">The query matched on a name</h3>
<p>&quot;How many home runs did Ken Griffey hit?&quot; There are two Ken Griffeys: the father, with 152, and the son, the Hall of Famer, with 630. A query that finds him with <code>WHERE first_name = &apos;Ken&apos; AND last_name = &apos;Griffey&apos;</code> adds them together and returns 782, a number that is exactly right for a question nobody asked.</p>
<p>The same thing turned up earlier in the day, on purpose. <code>WHERE park = &apos;Wrigley Field&apos;</code> finds 107 seasons, all in Chicago. <code>WHERE park LIKE &apos;%Wrigley%&apos;</code> finds 108, because it also catches the 1961 Los Angeles Angels, who played their first season at a different ballpark named Wrigley Field. The question you asked and the question you typed are not always the same question.</p>
<p>This is what the primary key lecture was for. Names are for humans: they get misspelled, shared and reused. An id is the one thing guaranteed to mean one row. When the answer is about a particular person, place or thing, look at the WHERE and ask whether it identifies that thing, or just something with the same name.</p>
<h3 id="rows-went-missing">Rows went missing</h3>
<p>&quot;Which team won the most games in 2002?&quot; The natural query is:</p>
<pre><code class="language-sql">SELECT year, name, wins
FROM teams
WHERE year = 2002
ORDER BY wins DESC
LIMIT 1;
</code></pre>
<p>It returns the New York Yankees, 103 wins. It is also wrong, or at least half of the answer: the Oakland Athletics won 103 games that year too. That is the Moneyball season. <code>LIMIT 1</code> kept the Yankees only because of the order the rows happened to be stored in. Change it to <code>LIMIT 2</code> and the A&apos;s appear.</p>
<p>A missing row is the hardest mistake to catch, because there is nothing on the screen to look at. You can read a wrong WHERE clause. You cannot read a row that is not there.</p>
<h2 id="you-can-test-a-feature-you-can-only-check-a-query">You can test a feature. You can only check a query.</h2>
<p>When a developer builds a feature or fixes a bug, there is a way to know it worked. Click through it the way a user would. Better, write a test: a small program that says what should happen and checks that it does, run every time the code changes. At the end of class you watched an agent do exactly that, adding a test for 140-character captions and running it.</p>
<p>A query does not get that. It runs, a number comes back, and nothing on the screen says whether it answered the question. Some teams build a small test database, stocked with the edge cases they already know about (the legacy rows, the duplicates, the weird old records), and run their queries against it. That helps. Mostly, though, someone spot-checks the result, and spot-checking takes knowing the data: its edge cases, its quirks, and the places where it is not as clean as it looks.</p>
<p>Look at the traps above again. The 2002 tie, the two Ken Griffeys, Cleveland under seven names, the other Wrigley Field: none of them was a SQL mistake. Each was a fact about the data that you had to already know to notice. And nobody hands you the expected result. If the stakeholder knew the answer, they would not be asking.</p>
<p>None of this is new with AI. It was true when analysts wrote every line of SQL by hand, and it is still true now that a chat writes it. What changed is how fast a confident answer arrives. The checking did not change.</p>
<h2 id="four-cheap-checks">Four cheap checks</h2>
<p>None of these need more SQL than you already know.</p>
<ol>
<li><strong>Say what you expect before you run it.</strong> Even roughly: &quot;a team, a hundred-ish wins, this century.&quot; A result that surprises you is the only alarm you get, and you cannot be surprised if you had no expectation.</li>
<li><strong>Ask for one more row than you need.</strong> If the question is &quot;which one&quot;, run it with <code>LIMIT 3</code>. Ties and near-ties show up immediately, and so does a second Ken Griffey.</li>
<li><strong>Count what came back.</strong> If you asked for the best team in every season since 1960, you should get at least 61 rows, one per season. Fewer means something was dropped. More is not necessarily wrong (in six of those seasons there was a tie), but you should know why.</li>
<li><strong>Read the WHERE out loud.</strong> Does it say what you asked? A name where an id should be, a <code>LIKE</code> where you meant an exact match, a year range that is off by one: the WHERE clause is where most wrong answers live, and it is usually one line.</li>
</ol>
<h2 id="when-the-analyst-is-good">When the analyst is good</h2>
<p>Some of you watched your chat get everything right: it named both readings of the losing question, split the two Ken Griffeys by id, or wrote the 2002 query so that it returned both teams. The better chats are good at this, and increasingly so.</p>
<p>Check anyway. The chat caught the 2002 tie because it remembers the 2002 season. It does not remember your company&apos;s customers, your pricing table or last quarter&apos;s refunds, and on that data it will write the same careful-looking SQL with nothing to check it against but you. Good analyst. Now check that it was right. The loop does not change.</p>
<h2 id="never-run-update-or-delete-without-where">Never run UPDATE or DELETE without WHERE</h2>
<p>Everything above is about reading. This is about the one time it stops being safe to get it wrong.</p>
<p><code>UPDATE</code> and <code>DELETE</code> change data, and both apply to every row the WHERE clause matches. With no WHERE clause, that is every row in the table. <code>DELETE FROM teams;</code> does not ask which teams. It removes all 2,955 of them.</p>
<p>In class you watched an AI agent (a chat that can run commands itself, not just suggest them) get asked to &quot;clear out the teams table so we can reload it from a fresh export.&quot; It wrote exactly that statement. It stopped and asked first only because the repository has a file, <code>AGENTS.md</code>, telling any agent working there to show a change to the data and wait for confirmation. Without that line, it runs.</p>
<p>Then it ran, and the table was empty. The database came back because in this class the database is a single file that lives in Git, and <code>git checkout db/baseball.sqlite3</code> restored it. Production databases do not work like that. Most of the time there is no undo, only a backup, if someone made one, from some time before the mistake.</p>
<p>So: when you see UPDATE or DELETE, from a chat, an agent, a colleague or yourself, find the WHERE before anything else. If there is not one, stop.</p>
<h2 id="the-thread-through-all-of-it">The thread through all of it</h2>
<p>Every trap in this post ran without an error. The SQL was valid, a number came back, and the only thing between that number and a decision was someone reading it. Writing SQL is increasingly something you will hand off. Deciding whether to believe it is not.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Working in a Codespace]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>Most weeks from here on start the same way and end the same way. This is the short version of both, plus how to reset the database and one settings fix for Windows. Keep it open the first few times; after that you will not need it.</p>
</blockquote>
<h2 id="before-week-2-have-a-chat-open">Before Week 2:</h2>]]></description><link>https://entr451.com/working-in-a-codespace/</link><guid isPermaLink="false">6abb3ef6d9019e19441c3286</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 29 Sep 2026 11:53:33 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>Most weeks from here on start the same way and end the same way. This is the short version of both, plus how to reset the database and one settings fix for Windows. Keep it open the first few times; after that you will not need it.</p>
</blockquote>
<h2 id="before-week-2-have-a-chat-open">Before Week 2: have a chat open</h2>
<p>In Week 2 you ask an AI chat questions about a database and decide whether to believe its answers. Bring whichever one you already use: ChatGPT, Claude, Gemini, Copilot. Any of them is fine. There is nothing to install and nothing to sign up for. Have it open in a second window before class starts.</p>
<h2 id="getting-started">Getting started</h2>
<ol>
<li>
<p><strong>Use this template.</strong> On the class repository&apos;s page, click <strong>Use this template</strong>, then<br>
<strong>Create a new repository</strong>. Pick your own account as the owner, keep the name, and click<br>
<strong>Create repository</strong>. You now have your own copy; nothing you do can affect anyone else&apos;s.</p>
</li>
<li>
<p><strong>Create the Codespace.</strong> In your new repository: <strong>Code</strong>, then the <strong>Codespaces</strong> tab,<br>
then <strong>Create codespace on main</strong>. The first start takes a few minutes. Let it finish; the<br>
terminal at the bottom is ready when it shows a prompt and stops scrolling.</p>
</li>
<li>
<p><strong>Start the server.</strong> In the terminal:</p>
<pre><code>bin/rails server
</code></pre>
<p>A box pops up offering to open the port. Click <strong>Open in Browser</strong>. If you miss the box, open the <strong>Ports</strong> tab next to the terminal and click the globe icon on port 3000.</p>
</li>
</ol>
<p>The server keeps running in that terminal until you stop it with <strong>Ctrl-C</strong>. If you need a terminal for anything else, open a second one with the <strong>+</strong> on the terminal panel.</p>
<p><strong>Coming back later.</strong> Do not create a second Codespace. Go to <code>github.com/codespaces</code>, or<br>
<strong>Code</strong> &gt; <strong>Codespaces</strong> on your repository, and open the one you already have. Your files are where you left them. The server is not; run <code>bin/rails server</code> again.</p>
<h2 id="windows-turn-on-right-click">Windows: turn on right-click</h2>
<p>On some Windows machines, right-clicking in the terminal does not open a menu, and the menu is how you find things like <strong>Clear</strong>. To fix it:</p>
<ol>
<li>Click the gear icon at the bottom left of the editor (the one inside the editor, not the one at the far left of your screen), then <strong>Settings</strong>. Not the command palette; the setting is not there.</li>
<li>Search for <code>right click behavior</code>.</li>
<li>Under <strong>Terminal &gt; Integrated: Right Click Behavior</strong>, yours probably says <code>copyPaste</code>. Change it to <code>selectWord</code>.</li>
</ol>
<p>It saves itself. Close the Settings tab and right-click in the terminal again. If you are on a Mac, you can skip this.</p>
<h2 id="resetting-the-database">Resetting the database</h2>
<p>In Week 2 the whole database is one file, <code>db/baseball.sqlite3</code>, and it is committed to the repository. So if anything damages it (a DELETE or UPDATE you did not mean, a table you dropped), you can put it back exactly as it was. In the terminal:</p>
<pre><code>git checkout db/baseball.sqlite3
</code></pre>
<p>Then refresh the page. The rows are back. The server can keep running while you do it.</p>
<p>This works as long as you have not committed the damage. If <code>db/baseball.sqlite3</code> ever shows up under <strong>Changes</strong> when you go to commit, run the line above first, then commit.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<ol>
<li><strong>Stage.</strong> Open <strong>Source Control</strong> in the left bar (the icon that looks like a branching line). Your changed files are listed under <strong>Changes</strong>. Click the <strong>+</strong> next to each one, or the <strong>+</strong> on the <strong>Changes</strong> heading to stage them all.</li>
<li><strong>Message, then commit.</strong> In the box above the list, write what you did in plain words, like <code>answered the three questions</code>. Click <strong>Commit</strong>.</li>
<li><strong>Push.</strong> The button turns into <strong>Sync Changes</strong>. Click it. That sends your commit to GitHub; until you do, it exists only inside the Codespace. Check your repository&apos;s page on github.com: your commit message should be there.</li>
<li><strong>Stop the Codespace.</strong> On your repository&apos;s page: <strong>Code</strong> &gt; <strong>Codespaces</strong>, the three dots next to yours, <strong>Stop codespace</strong>. It stops itself after thirty minutes idle, but doing it yourself is the habit, and Codespaces hours come out of your own monthly allowance.</li>
</ol>
<p>Stopping is not deleting. Your Codespace and everything in it are still there next time.</p>
<h2 id="when-it-does-not-work">When it does not work</h2>
<p>Post in <code>#fall-2026</code> on Slack with a screenshot and the step you were on. If it is during class, raise your hand; an FA will come to you.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Open source and licensing, the longer version]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is one of two posts from the first week. The other, <a href="https://entr451.com/git-and-github/">Git and GitHub</a>, is the tools: what they are and how a change gets into a codebase. This one is about whose code you are willing to build on in the first place.</p>
</blockquote>
<p>In class we have (or</p>]]></description><link>https://entr451.com/open-source-and-licensing/</link><guid isPermaLink="false">6ab2cfdbd9019e19441c3256</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 22 Sep 2026 19:10:31 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is one of two posts from the first week. The other, <a href="https://entr451.com/git-and-github/">Git and GitHub</a>, is the tools: what they are and how a change gets into a codebase. This one is about whose code you are willing to build on in the first place.</p>
</blockquote>
<p>In class we have (or will) spend a little time on open source and licensing -- enough to give you the shape of it and not much else. This is the longer version, for when you need it.</p>
<p>And you likely will need it. Not because you are going to become a lawyer, but because at some point somebody on your team is going to say &quot;let&apos;s just use this library,&quot; and you are going to be the person who decides. That decision has terms attached to it, and the terms occasionally bite.</p>
<h2 id="closed-and-open">Closed and open</h2>
<p>Software gets built and brought to market two ways.</p>
<p><strong>Closed-source</strong> software is a commercial asset. The code is seen only by the people working on it, and the intellectual property belongs to whoever owns the company. Most software that is sold, whether packaged or as a service, works this way. If a company&apos;s secret sauce has anything to do with the code, they are not going to show you the code.</p>
<p><strong>Open-source</strong> software has its source code available to the public. That is the whole definition. Note what it does not say: it does not say free, it does not say non-commercial, and it does not say nobody owns it. Plenty of open-source software is built by large companies, for commercial reasons, and is owned by someone specific.</p>
<h2 id="why-anyone-gives-work-away">Why anyone gives work away</h2>
<p>The reasonable first question is why a developer or a company would put thousands of hours into something and then hand it over.</p>
<ul>
<li><strong>Crowdsourcing.</strong> Publish the code and other people find your bugs, and sometimes fix them. The project gets better than you could have made it alone.</li>
<li><strong>Knowing what is in your food.</strong> Telegram publishes its source so it can prove it is not doing anything malicious with your data. A closed product cannot make that promise; it can only assert it.</li>
<li><strong>Reputation.</strong> An individual developer open-sources a piece of their work so that employers and future partners can see what they are capable of.</li>
<li><strong>Recruiting.</strong> A company open-sources something interesting so that the developers they want to hire come and look at it.</li>
<li><strong>The ecosystem, which is really marketing.</strong> Google does not open-source all of Chrome, but the engine underneath it is open. Other browsers get built on that engine, and when one of those browsers does not work out, moving to Chrome is easy, because it is the thing you were already standing on. Being the foundation other people build on is a competitive position, not charity.</li>
</ul>
<p>The biggest reason underneath all of those is less strategic than any of them. Software is built the way science is built, by people standing on each other&apos;s work. Donald Knuth, the Stanford computer scientist, put it this way:</p>
<blockquote>
<p>People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.</p>
</blockquote>
<p>That has never been more true than it is right now. The reason you can build something sophisticated in a quarter is that thousands of people already built the parts you are standing on.</p>
<p>Here is what that looks like in practice. Every application with users has to handle user security: passwords, sessions, encryption. Almost no business is <em>about</em> user security. It is table stakes, it is subtle, it is easy to get wrong in ways that hurt people, and writing it yourself costs you weeks you needed for the thing your product is actually for. So you take a library that already exists, that thousands of other applications have already stress tested, and you spend your weeks on your own problem instead.</p>
<p>Do not fool yourself into thinking you are going to write all of your own code. Nobody does this. You would never want to, because you would spend all your time in the foundations and never get to the interesting part.</p>
<h2 id="licensing-the-two-families">Licensing: the two families</h2>
<p>Open source is not a free-for-all. Every open-source project carries a license, and the license is a real legal document with real terms. There are many of them and there are endless variations, and when it matters you read the actual text or you ask someone whose job that is. But nearly all of them fall into one of two families.</p>
<p><strong>Permissive.</strong> Do what you like. Use it, change it, sell it, bundle it into something closed-source. Most permissive licenses ask for one thing in return: keep the copyright notice around so people can tell where the code came from. The MIT license is the most common one you will see, and Apache 2.0 is the other big one, which adds some explicit language about patents.</p>
<p><strong>Copyleft.</strong> You may use this, and you may change it, but if you distribute what you built, the result has to carry this same license. The GNU General Public License, the GPL, is the one you will run into. The point is reciprocity: the code was given to you open, and whatever you make out of it goes out open too. Sometimes it is called &quot;share-alike,&quot; which is a better name for what it actually does.</p>
<p><strong>One thing worth being precise about</strong>, because the short version in class makes it easy to get backwards: copyleft does not mean the code is frozen. You are absolutely allowed to modify GPL code. Modifying it is the point. What you owe in return is that your modified version goes out under the same license, so that the next person gets the same freedom you got. The obligation is on <em>what you publish</em>, not on <em>what you touch</em>.</p>
<p>It is permissive licenses, not copyleft ones, where &quot;leave it alone&quot; comes closest to the truth, and even there the obligation is usually just to preserve the notice.</p>
<p><a href="https://choosealicense.com/">choosealicense.com</a> is a genuinely good, short guide to picking one, and it is the right link to send a co-founder who is about to make this decision for your company.</p>
<h2 id="case-study-the-linux-family-tree">Case study: the Linux family tree</h2>
<p>Linux is the most widely deployed operating system in the world. Not Windows, not macOS. Estimates move around and depend on what you are counting, but the large majority of the web servers on the internet run some flavor of Linux, as does every one of the world&apos;s fastest supercomputers, and so does every Android phone. It was written by Linus Torvalds, who also<br>
later wrote Git, which is the other thing we covered in week one. He has had a productive career.</p>
<p>The core of Linux, called the kernel, is licensed under GPLv2. Copyleft. That single fact shapes an entire industry, and the shape is worth following.</p>
<p><strong>Distributions.</strong> Ubuntu, Debian, Red Hat and Fedora are all built around that same kernel. Their kernel remains GPL. They wrap it in package managers, installers, desktop environments and support contracts, and those pieces carry their own licenses. Red Hat built a large public company selling support and certification for software anyone could download for nothing.</p>
<p><strong>Android.</strong> In 2008 a team at Google built a phone operating system on the Linux kernel. Android&apos;s own layers, the parts Google wrote, are licensed under Apache 2.0, which is permissive. That combination surprises people, and it is worth understanding why it is allowed, because it is the single most useful thing in this whole post.</p>
<p>It is not because Google left the kernel alone. Google modifies the kernel substantially for Android, and those modifications are published under the GPL, exactly as the license requires.</p>
<p>It is because of a boundary. Torvalds attached a note to the kernel&apos;s license saying that ordinary user-space programs that call into the kernel through normal system calls are not derivative works of the kernel. They are just using it. Android&apos;s own layers sit on the user-space side of that line, so they do not inherit the kernel&apos;s license and Google can license them as it chooses. Google also deliberately built Android&apos;s C library from permissively licensed code rather than the copyleft one Linux distributions normally use, for exactly this reason.</p>
<p>The lesson generalizes past Android: <strong>where the boundary falls between your code and the copyleft code is the question that decides your obligations.</strong> Linking a GPL library directly into your application is a very different act from running a GPL program as a separate process and talking to it. If your company is ever seriously weighing this, that is the question to bring to a lawyer, and it is a question you can now ask precisely.</p>
<p><strong>On top of Android.</strong> Because Android is permissive, anyone can fork it and build a commercial product. Amazon did, and Fire OS runs the Kindle Fire line. Meta built the software for its VR headsets on it. Peloton runs it on the screen bolted to the bike. None of those companies had to open-source the product they built.</p>
<p>A small correction to something easy to assume here: Amazon is not prevented from charging money for Fire OS. It could. It does not, because Amazon&apos;s interest is in selling devices and content, and giving the operating system away with the hardware serves that. The constraint is a business decision, not a licensing one.</p>
<p>Follow that chain once more, because it is the point of the whole case study. A copyleft kernel, given away in 1991. A permissive operating system built on it by one of the largest companies in the world. Closed commercial products built on that, by three more. Your closed-source product will sit on a stack like this one, whether or not you ever look down.</p>
<h2 id="the-four-questions-you-asked-in-class">The four questions you asked in class</h2>
<p>These came from the room, not from the slides, and they are the right questions.</p>
<h3 id="how-is-any-of-this-actually-enforced">How is any of this actually enforced?</h3>
<p>Mostly the way any contract is: somebody notices. An employee, a competitor, a customer who asks for the source, or one of the organizations that exists specifically to notice. The Free Software Foundation and the Software Freedom Conservancy have both pursued GPL violations, usually by asking first and suing rarely.</p>
<p>The famous case is a router. In the early 2000s Linksys shipped the WRT54G, a wireless router that turned out to be running Linux inside. The GPL obliged them to release the source for what they shipped, they had not, and the community noticed and pushed. Cisco, which by then owned Linksys, released it. The released code became OpenWrt, which is still running on routers today. The violation ended up producing a piece of open-source infrastructure.</p>
<p>Here is the version you are more likely to live through, which happened to a company one of us worked with. They were using an open-source front-end framework. The framework changed its license on a new version. They upgraded, because they wanted the new version, and nobody on the team connected the upgrade to the terms. About a year later somebody worked out that they had been out of compliance the whole time, and it was settled with a payment.</p>
<p>Nobody was reckless. They upgraded a dependency, which is a thing engineering teams do dozens of times a quarter without anyone thinking about it, and the terms had moved underneath them.</p>
<h3 id="does-linux-force-updates-on-you">Does Linux force updates on you?</h3>
<p>No. The project keeps developing, and you run whatever version you chose to run. Nobody can reach into your product and change it.</p>
<p>You will usually want the updates anyway, because that is where the security patches are and because software that stays still gets stranded as everything around it moves. But taking them is your decision, and it is a decision, which matters for the next question.</p>
<h3 id="can-a-project-change-its-license-retroactively">Can a project change its license retroactively?</h3>
<p>Not for the version you already have. The copy you received came with terms, and those terms came with it.</p>
<p>The next version is a different matter. A project can license its next release however it likes, and if you upgrade, you have accepted the new terms. That is the trap in the story above, and it is worth stating plainly: <strong>the risk does not arrive when the license changes. It arrives when you upgrade.</strong></p>
<p>This is not hypothetical or rare. Over the last several years, a number of widely used projects have changed their terms on a new version, sometimes to a license that is no longer open source at all. Redis, MongoDB, Elasticsearch and HashiCorp&apos;s Terraform all did some version of this, and in several cases the community responded by forking the last version under the old terms and continuing from there. If your product depends on something, a license change upstream is a real business event, not a legal footnote.</p>
<p>The practical defense is unglamorous and it is the same defense as for every other dependency risk: know what you depend on, and have someone actually look when a major version lands.</p>
<h3 id="does-everything-built-on-copyleft-code-have-to-be-open-source">Does everything built on copyleft code have to be open source?</h3>
<p>No, and the Android section above is the long answer. The short answer is that it depends on whether what you built is a derivative work of the copyleft code or a separate thing that uses it, and that boundary is technical as much as legal.</p>
<p>What is not optional: the copyleft code itself, and your changes to it, stay under that license and have to be available to the people you distributed it to. You cannot take GPL code private. You can, in many architectures, build something of your own next to it.</p>
<h3 id="and-one-you-asked-that-we-could-not-answer">And one you asked that we could not answer</h3>
<p>Somebody asked why the authors of copyleft projects care, since a permissive license would get their code used more widely. It is a good question and it deserves a real answer.</p>
<p>The answer is that for the people who started this, wide adoption was never the goal. Richard Stallman and the Free Software Foundation designed the GPL in the 1980s to protect a commons. The worry was specific: that software written to be free would get absorbed into closed products, improved there, and that the improvements would never come back, leaving the open version permanently behind. Copyleft is the mechanism that stops that. It is a one-way ratchet, and every improvement anyone distributes lands back in the pool.</p>
<p>Permissive licensing makes a different bet, which is that the widest possible adoption is worth more than the guarantee of reciprocity. Both bets have paid off enormously. They are simply different bets, and a project&apos;s license tells you which one its authors made.</p>
<h2 id="reading-a-repository-before-you-depend-on-it">Reading a repository before you depend on it</h2>
<p>The part of this that will matter to you most often is not the law. It is judgment: deciding whether some project you found is something you want your product to stand on. You will make this call many times, and usually in about four minutes.</p>
<p>Open the repository on GitHub and look at these, roughly in this order.</p>
<ol>
<li><strong>The license.</strong> It is listed in the sidebar. If there is no license file at all, the default is that you have no rights to use it, whatever the author intended by putting it in public. &quot;No license&quot; is a red flag, not a green one.</li>
<li><strong>The last commit.</strong> Near the top of the file list. A project last touched four years ago is a project you are adopting, not using.</li>
<li><strong>Issues and pull requests.</strong> Do not just read the count. Open the tabs. Are maintainers replying? Are pull requests getting merged or quietly piling up? An open issue list is healthy. An unanswered one is not.</li>
<li><strong>Releases.</strong> Regular releases mean somebody is still steering.</li>
<li><strong>Who is behind it.</strong> One person&apos;s side project and a thing maintained by a company or a foundation carry very different risk. Neither is disqualifying. You just want to know which one you are picking.</li>
<li><strong>Stars and forks.</strong> Useful, weak signals. Stars are closer to a bookmark count than a quality score, and old popular projects keep their stars long after the maintainers have moved on. Read them alongside the last commit, never alone.</li>
<li><strong>Documentation, and a demo if there is one.</strong> Many projects run a live demo page. It is the fastest way to find out whether the thing does what you think it does.</li>
</ol>
<p>Three worth opening, because they are good examples of different shapes:</p>
<ul>
<li><a href="https://github.com/microsoft/vscode">microsoft/vscode</a>, the editor you are using in your Codespace, maintained by a large company in the open.</li>
<li><a href="https://github.com/nasa/openmct">nasa/openmct</a>, NASA&apos;s mission control framework, with a demo page you can click through.</li>
<li><a href="https://github.com/chromium/chromium">chromium/chromium</a>, the engine underneath Chrome and most other browsers, and an example of the ecosystem argument from the top of this post.</li>
</ul>
<h2 id="what-this-is-really-about">What this is really about</h2>
<p>You are not going to be the person reading the license text. You are going to be the person who decides whether the team takes the dependency, and who gets asked, a year later, why the company is having this conversation with a lawyer.</p>
<p>The three things worth carrying out of here: <strong>open source is not free of terms</strong>, <strong>the terms change on upgrade and not before</strong>, and <strong>where the boundary falls between your code and theirs is the question that determines what you owe.</strong> Everything else you can look up.</p>
<hr>
<p>Next: <a href="https://entr451.com/practice-github/">Practice: GitHub</a>, where you run the loop once on your own. If you have not read <a href="https://entr451.com/git-and-github/">Git and GitHub</a> yet, read that first; it covers the<br>
difference between the two and how a change actually gets into a codebase.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Practice: GitHub]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In class you ran the loop once: a branch, a file, a commit, a pull request, somebody else&apos;s review, a merge. You did it in a room with forty other people doing the same thing and an FA two seats away.</p>
<p>This week you do it again, alone,</p>]]></description><link>https://entr451.com/practice-github/</link><guid isPermaLink="false">6ab2d11cd9019e19441c3267</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 22 Sep 2026 19:04:56 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In class you ran the loop once: a branch, a file, a commit, a pull request, somebody else&apos;s review, a merge. You did it in a room with forty other people doing the same thing and an FA two seats away.</p>
<p>This week you do it again, alone, on a repository of your own where nothing can go wrong. That is the entire point of this exercise. The first time you follow along. The second time you find out what you actually know.</p>
<p>It should take about thirty minutes. There is nothing to turn in.</p>
<h2 id="part-0-get-your-own-copy">Part 0: get your own copy</h2>
<p>If you already made your own <code>hello-world</code> during class, skip this.</p>
<p>Go to <code>github.com/dsgn425-fall2026/hello-world</code>, click the green <strong>Use this template</strong> button, then <strong>Create a new repository</strong>. Owner: your own account, not the class organization. Name: <code>hello-world</code>. Leave the visibility on <strong>Private</strong>. Click <strong>Create repository</strong>.</p>
<p>Check the name at the top left of the page. It should read <code>your-username/hello-world</code>. If it still says <code>dsgn425-fall2026/hello-world</code>, you are looking at the template, not your copy; go back and click <strong>Use this template</strong>.</p>
<p>It is yours now. You cannot break anything that belongs to anyone else, which is why we are doing this here rather than in the class <code>roster</code> repo.</p>
<h2 id="part-1-the-loop-by-yourself">Part 1: the loop, by yourself</h2>
<p>Open your copy and follow its README from step 2 to step 6. Do not read ahead and do not copy what you did in class from memory. Work it through.</p>
<p>Branch, change something, commit with a real message, publish, open a pull request, merge it, delete the branch.</p>
<p>Two differences from class, and both are worth noticing:</p>
<p><strong>You are the whole team.</strong> In the class repo a classmate had to approve your pull request. Here nobody does, so you get to read your own change on the <strong>Files changed</strong> tab and merge it yourself. Read it anyway, properly, as though somebody else wrote it. That habit is the one this course is really trying to build.</p>
<p><strong>Nothing is protected.</strong> Your repo has no rule stopping you from committing straight to <code>main</code>. You could skip the branch entirely and it would work. Make the branch anyway. The habit has to survive the absence of a rule enforcing it, or it is not a habit.</p>
<p>If you want the reasoning behind any of it, rather than the buttons, that is in <a href="https://entr451.com/git-and-github/">Git and GitHub</a>.</p>
<h2 id="part-2-the-same-loop-from-the-terminal">Part 2: the same loop, from the terminal</h2>
<p>Now do it a second time without touching the file explorer. Everything below happens in the Codespace terminal, which you open with <strong>View &gt; Terminal</strong> from the hamburger menu.</p>
<p>This half exists because the command line is the part of the first class that fades fastest if you never touch it again. Ten minutes now is worth an hour later.</p>
<p>Start on a new branch. Bottom-left corner, <strong>Create new branch...</strong>, call it <code>terminal-practice</code>.</p>
<p><strong>Find out where you are.</strong></p>
<pre><code>pwd
</code></pre>
<p>It prints your current directory, which should end in <code>/hello-world</code>. Then:</p>
<pre><code>ls
</code></pre>
<p>lists what is in it. You should recognize the files from the Explorer. Now:</p>
<pre><code>ls -a
</code></pre>
<p>The same list, plus the hidden files, the ones whose names start with a dot. There are more files in a repository than the Explorer shows you by default, and <code>.devcontainer</code> is how your Codespace knew what to build.</p>
<p><strong>Run the programs that are already there.</strong></p>
<pre><code>ruby code/hello.rb
</code></pre>
<p>prints a greeting. Then:</p>
<pre><code>ruby code/infinite-tacos.rb
</code></pre>
<p>which does not stop on its own. Press <strong>Ctrl-C</strong> to kill it. You need this more often than you would think, and nobody tells you about it until you need it.</p>
<p><strong>Make something.</strong></p>
<pre><code>mkdir notes
</code></pre>
<p>creates a folder. Confirm it with <code>ls</code>. Then:</p>
<pre><code>touch notes/week-01.md
</code></pre>
<p>creates an empty file inside it. Open <code>notes/week-01.md</code> from the Explorer, write two or three lines about anything from the first class, and save with Ctrl-S.</p>
<p><strong>Move something.</strong></p>
<p>The repo has an <code>images</code> folder with a couple of files in it. Pick one and move it to the top level, then move it back:</p>
<pre><code>mv images/grogu.jpg .
ls
mv grogu.jpg images
ls
</code></pre>
<p>The <code>.</code> means &quot;here, the directory I am in.&quot; Watch the Explorer on the left as you run each one. The file jumps. You can of course do all of this by dragging with a mouse. The point is that you now know the other way, and the other way is the one that works over a terminal connection to a server at two in the morning.</p>
<p><strong>One useful trick each.</strong> Start typing a directory name and press <strong>Tab</strong>; the shell finishes it for you, and if nothing happens it is telling you that what you typed does not exist. Press the <strong>up arrow</strong> to bring back the last command you ran, which saves you retyping a long one with a typo in it.</p>
<p><strong>Then put it through the loop.</strong> Source Control, stage, commit with a message that says what you did, <strong>Publish Branch</strong>, open the pull request on github.com, read the diff, merge, delete the branch.</p>
<h2 id="before-you-close-the-tab">Before you close the tab</h2>
<p>Stop your Codespace. On the repo page, <strong>Code</strong> &gt; <strong>Codespaces</strong>, the three dots, <strong>Stop codespace</strong>. It stops itself after thirty minutes idle, but doing it yourself is the habit, and Codespaces hours come out of your own monthly allowance.</p>
<h2 id="what-to-check-before-you-call-it-done">What to check before you call it done</h2>
<ul>
<li>Your repository&apos;s <code>main</code> branch contains both changes.</li>
<li>The commit history shows how they got there, and the messages say what happened. If any of them says <code>update</code> or <code>tried again</code>, go and look at why that is useless to you two weeks from now.</li>
<li>Both branches are gone, because you deleted them after merging.</li>
<li>You can say, without looking anything up, what the difference is between a commit and a pull request.</li>
</ul>
<p>That last one is the actual assessment. The rest is just evidence.</p>
<h2 id="if-you-got-stuck">If you got stuck</h2>
<p>Post in <code>#fall-2026</code> on Slack and say where it stopped working. Getting stuck is not a failure mode here, it is the expected experience of week one, and the stuck point is usually the same three or four things. The troubleshooting list at the bottom of the class <code>roster</code> repo&apos;s README covers most of them.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Git and GitHub]]></title><description><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is one of two posts from the first week. This one is the tools: what Git and GitHub are, and how a change actually gets into a codebase. The other is <a href="https://entr451.com/open-source-and-licensing/">Open source and licensing, the longer version</a>, which is about deciding whose code you are willing to build</p></blockquote>]]></description><link>https://entr451.com/git-and-github/</link><guid isPermaLink="false">6ab2ce78d9019e19441c3242</guid><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Tue, 22 Sep 2026 18:55:43 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><blockquote>
<p>This is one of two posts from the first week. This one is the tools: what Git and GitHub are, and how a change actually gets into a codebase. The other is <a href="https://entr451.com/open-source-and-licensing/">Open source and licensing, the longer version</a>, which is about deciding whose code you are willing to build on. Start here; that one uses the vocabulary this one sets up.</p>
</blockquote>
<h2 id="git-and-github-are-not-the-same-thing">Git and GitHub are not the same thing</h2>
<p>Nearly everyone trips over these two names, and it is worth thirty seconds of precision, because more decisions hang off the distinction than you would guess.</p>
<p><strong>Git is software.</strong> It is version control for source code: it stores your files, keeps their entire history, and lets several people work on the same project without overwriting each other. Linus Torvalds wrote it in 2005 to manage development of the Linux kernel. It is free and open source, it runs on your own machine (or, this quarter, in your Codespace), and no company owns it.</p>
<p><strong>GitHub is a company.</strong> Founded in 2008, bought by Microsoft in 2018 for around seven and a half billion dollars. Its business is hosting Git repositories and selling the things built around them: a web interface, permissions, issue tracking, code review tools, automation.</p>
<p>The relationship between the two names is exactly that, and nothing more. GitHub hosts Git.</p>
<p>If it helps: <strong>Git is email, GitHub is Gmail.</strong> Email is a protocol that nobody owns and that many companies provide. Gmail is one company&apos;s product for using it, with its own interface and its own features layered on top. You can leave Gmail and still have email.</p>
<p>Git is the thing. GitHub is a place to keep the thing.</p>
<h3 id="other-places-to-keep-the-thing">Other places to keep the thing</h3>
<p>GitHub is the largest by a wide margin, which is why this course uses it and why it is the one worth knowing. It is not the only one, and a few of these will turn up in your working life:</p>
<ul>
<li><strong>GitLab</strong>, the closest competitor, and the one most often chosen by companies that want to run the whole thing on their own servers.</li>
<li><strong>Bitbucket</strong>, from Atlassian, common in organizations already using Jira.</li>
<li><strong>Azure DevOps</strong>, also Microsoft, common in large enterprises.</li>
<li><strong>Self-hosted</strong>, using software like Gitea or Forgejo, or a plain Git server with no web interface at all. Git needs no company to function.</li>
</ul>
<h3 id="why-the-distinction-is-worth-money">Why the distinction is worth money</h3>
<p>Here is the part that matters when you are the one choosing.</p>
<p>Your project&apos;s entire history lives inside the repository itself, in a hidden folder that comes along whenever anyone copies it. Moving a project from one host to another is close to trivial: clone it, point it at the new host, push. Nobody holds your code hostage.</p>
<p>What does not move is everything the host added. Your issues, your pull requests and the discussions on them, your review history, your automation, your integrations. Those are GitHub&apos;s, in GitHub&apos;s shapes, and leaving means leaving them behind or paying somebody to convert them.</p>
<p>That asymmetry is the actual product. It is also the shape of most software-as-a-service lock-in you will ever evaluate: the thing you thought you were buying is portable, and the thing that accumulated around it is not. Worth recognizing the pattern here, where the stakes are low, so you recognize it later when they are not.</p>
<h2 id="what-git-actually-does">What Git actually does</h2>
<p>Think about version history in a document editor: you can see what changed, who changed it, and roll back to how it looked last Tuesday. Git is that, for code, with the history treated as the important part rather than a convenience.</p>
<p>Because Git watches every file in a project continuously, it knows the entire history of the work: what was added, modified and deleted, by whom, and when. That is what makes the rest possible.</p>
<ul>
<li><strong>Attribution.</strong> Who contributed what, and when.</li>
<li><strong>Time travel.</strong> View or return to the project as it stood at any earlier point.</li>
<li><strong>Branching.</strong> Several versions of the same project, running in parallel from a common starting point, which is how more than one person works on something at once without chaos.</li>
</ul>
<p>Every professional software team uses version control, and in practice almost all of them use Git. Once it arrived, the alternatives largely emptied out.</p>
<h2 id="repositories">Repositories</h2>
<p>A Git project lives in a <strong>repository</strong>, usually one repository per project. Hosting repositories is GitHub&apos;s main business.</p>
<p>A GitHub user or organization holds repositories. Microsoft&apos;s organization on GitHub holds a great many, one of which is the source for Visual Studio Code, the editor running in your Codespace. Your own account starts empty. That changes in the first class.</p>
<p>There are two ways to make one:</p>
<ul>
<li><strong>New repository</strong>, starting from nothing or from code already on your computer.</li>
<li><strong>Use this template</strong>, which copies an existing repository&apos;s files into a brand new repository of your own. This is the one we use most in this course, because it hands you a working starting point instead of an empty folder.</li>
</ul>
<p>To try the second one: go to <code>github.com/dsgn425-fall2026/hello-world</code>, click <strong>Use this template</strong>, name the new repository <code>hello-world</code>, leave it private, and create it. It lands in your own account in a few seconds, and it is yours.</p>
<p>Have a look around. A repository is a folder of files and folders, and not much more than that. You can add files and edit the ones already there, right in the browser.</p>
<h2 id="your-first-commit">Your first commit</h2>
<p>Open <code>README.md</code> and click the pencil icon. Change anything you like, then scroll to the bottom, to <strong>Commit changes</strong>.</p>
<p>The first box is the <strong>commit message</strong>. It is pre-filled with something generic like <code>Update README.md</code>, and you should replace it. The message is a note to your future self and to everybody else about what you did and why, and it is the single most durable piece of communication in a codebase. Write what changed, in plain words.</p>
<p>Click <strong>Commit changes</strong>. That takes the work you did, one coherent unit of it, and records it permanently in the repository&apos;s history.</p>
<p>A commit is a snapshot with a note attached. That is the whole idea, and everything else in Git is built out of it.</p>
<p>Committing straight onto the project the way we just did is fine when the project is yours and nobody else is touching it. It stops being fine immediately after that, and the rest of this post is what teams do instead.</p>
<h2 id="branches">Branches</h2>
<p>A repository has a main line of development, called <code>main</code>. Treat it as the official version: the one that works, the one that ships, the one everybody else pulls from.</p>
<p>A <strong>branch</strong> is a separate line of work that starts from a copy of <code>main</code> at a moment in time. You make one, you do your work on it, and <code>main</code> is untouched the entire time. When the work is done and somebody has looked at it, the branch gets folded back in.</p>
<p>The reason this matters is not tidiness. It is that real work is unfinished for most of its life. Halfway through a change, the code is usually broken, and if you were working directly on <code>main</code> then everybody else is now working on top of your broken half-change. A branch is where something is allowed to be incomplete without hurting anyone.</p>
<p>The second reason is that several people can do this at once. Five people on five branches are not in each other&apos;s way, and the question of how their work fits together gets asked once, deliberately, when each branch comes back, instead of continuously and by accident.</p>
<p>Two practical notes. Name the branch for what you are doing, not for yourself: <code>add-search</code> tells a colleague something, <code>bens-branch</code> does not. And do not hoard a branch. The longer it sits away from <code>main</code>, the more <code>main</code> moves underneath it and the harder the eventual reconciliation gets. Branches are cheap and meant to be short-lived, which is the opposite of<br>
the instinct most people arrive with.</p>
<h2 id="pull-requests">Pull requests</h2>
<p>Here is a distinction worth holding onto, because it follows the Git and GitHub split from the top of this post: <strong>branches and commits are Git. Pull requests are not.</strong></p>
<p>A pull request is a GitHub feature, built on top of Git. It is a request, addressed to the people who own the project, saying: I did this work on my branch, please pull it into <code>main</code>. GitLab calls the same thing a merge request, which is arguably the better name.</p>
<p>It is three things at once, and the third is the one people miss.</p>
<ol>
<li><strong>A proposal.</strong> Nothing has happened yet. <code>main</code> is unchanged until somebody merges.</li>
<li><strong>A place to talk.</strong> The pull request shows the diff: every line added, removed or changed. Anyone can comment on the whole thing, or on one specific line. Conversation happens against the actual change rather than in a meeting about the change.</li>
<li><strong>A record.</strong> It survives. In two years, when somebody asks why a thing works the way it does, the pull request is where the answer is: what changed, who asked for it, what the objections were, and who decided. Most of the institutional memory of a codebase lives in old pull requests.</li>
</ol>
<p>On any serious team, <code>main</code> is protected, which means nobody can push to it directly, not even the person who owns the company. The pull request is not the polite option. It is the only door.</p>
<h2 id="code-review">Code review</h2>
<p>Somebody other than the author has to look at the change and say yes. That is code review, and it is the part of this that will follow you furthest, because you may well end up on the approving side of it without ever writing the code yourself.</p>
<p>A reviewer opens the <strong>Files changed</strong> tab and answers three questions:</p>
<ol>
<li>Does the change do what the pull request says it does?</li>
<li>Does the commit message tell you what changed without opening the diff?</li>
<li>Would you merge this?</li>
</ol>
<p>Then they leave at least one comment on a line, and submit the review as either <strong>Approve</strong> or <strong>Request changes</strong>. If they requested changes, the author fixes it on the same branch and commits again, the pull request updates itself, and the reviewer looks again. That loop is the job.</p>
<p>Notice what is not on that list. Nothing about whether the code is clever, and nothing that requires you to be able to have written it yourself. Review is mostly about intent: does this do what it claims, would somebody understand it later, and am I willing to put my name on letting it in. Those are judgment questions, and they are the same questions you would ask about anything else your team proposes to ship.</p>
<p>The checklist grows as the course goes on. The three questions do not change.</p>
<p>One rule that is worth understanding rather than memorizing: GitHub will not let you approve your own pull request. Nobody reviews their own work. That is not a technicality, it is the whole point of the mechanism.</p>
<h2 id="the-loop-in-one-place">The loop, in one place</h2>
<p>Every change to every serious codebase goes through some version of this.</p>
<ol>
<li><strong>Branch.</strong> Off <code>main</code>, named for what you are about to do.</li>
<li><strong>Change.</strong> On the branch, where being half-finished is safe.</li>
<li><strong>Commit.</strong> One coherent unit of work, with a message that says what it was.</li>
<li><strong>Publish.</strong> The branch goes up to GitHub.</li>
<li><strong>Pull request.</strong> The proposal to change <code>main</code>.</li>
<li><strong>Review.</strong> Somebody else reads it and says yes, or says not yet.</li>
<li><strong>Merge.</strong> It lands on <code>main</code>.</li>
<li><strong>Delete the branch.</strong> It served its purpose.</li>
</ol>
<p>You will run that loop by hand a few times in this course until it is boring. That is the intended outcome. The mechanics are meant to become automatic so that your attention can go to the only part that actually needs a person: step 6.</p>
<h2 id="where-this-goes-next">Where this goes next</h2>
<ul>
<li><a href="https://entr451.com/practice-github/">Practice: GitHub</a> to run the whole loop once on your own, with nobody else&apos;s repository at risk.</li>
<li><a href="https://entr451.com/open-source-and-licensing/">Open source and licensing, the longer version</a>, if you came here from the class and skipped it.</li>
<li><a href="https://entr451.com/command-line-basics-and-the-development-environment/">Command-line basics and the development environment</a> for what comes after the browser.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Deployment with Render]]></title><description><![CDATA[<p>Here are detailed instructions on how to get your application hosted in a production-quality environment!</p><h3 id="preparing-your-application">PREPARING YOUR APPLICATION</h3><p>In your <code>Gemfile</code>, it may already look like it does below (specifically with the <code>sqlite3</code> gem nested inside a <code>group :development</code> block and a <code>pg</code> gem nested inside a <code>group :production</code> block)</p>]]></description><link>https://entr451.com/deployment-with-render/</link><guid isPermaLink="false">65e8a415d9019e19441c2d57</guid><category><![CDATA[Ship It]]></category><dc:creator><![CDATA[Ben Block]]></dc:creator><pubDate>Wed, 06 Mar 2024 19:33:10 GMT</pubDate><content:encoded><![CDATA[<p>Here are detailed instructions on how to get your application hosted in a production-quality environment!</p><h3 id="preparing-your-application">PREPARING YOUR APPLICATION</h3><p>In your <code>Gemfile</code>, it may already look like it does below (specifically with the <code>sqlite3</code> gem nested inside a <code>group :development</code> block and a <code>pg</code> gem nested inside a <code>group :production</code> block). &#xA0;If not, delete <code>gem &quot;sqlite3&quot;, &quot;~&gt; 1.4&quot;</code> and add the following:</p><pre><code>group :development, :test do
  gem &quot;sqlite3&quot;, &quot;~&gt; 1.4&quot;
  # note: keep other gems that were already in this group (e.g. &quot;debug&quot;)
end

group :production do
  gem &apos;pg&apos;
end</code></pre><p><em>Note: there was likely already a </em><code>group :development, :test</code><em> block in your code. If so, keep that code and just add the </em><code>sqlite3</code><em> gem to that group. &#xA0;Don&apos;t delete any other gems in the group. &#xA0;For example, your code may now look like this:</em></p><pre><code>group :development, :test do
  gem &quot;debug&quot;, platforms: %i[ mri mingw x64_mingw ]
  gem &quot;sqlite3&quot;, &quot;~&gt; 1.4&quot;
end</code></pre><p>Once you&apos;ve moved the <code>sqlite3</code> gem and added the <code>pg</code> gem, execute the change in your <code>Gemfile</code> by running:</p><pre><code>bundle install</code></pre><p>These changes tell your Rails application to use the industrial-strength PostgreSQL database in production, instead of the sqlite3 database we&apos;ve been using for development. &#xA0;The database will behave the same for our purposes, but is ready for production-level traffic and future scalability.</p><p>Next, you&apos;ll need to update the <code>config/database.yml</code> file. &#xA0;With the <code>pg</code> library installed for production, we also need to configure the app so that it knows where to find the production database. &#xA0;Currently, it lives in a file in the <code>db</code> directory (<code>db/development.sqlite3</code>), but in production the database is managed separately.</p><p>If it&apos;s not already there, add the following to the config file:</p><pre><code>production:
  &lt;&lt;: *default
  adapter: postgresql
  database: my_app_production
  username: my_app
  password: &lt;%= ENV[&quot;MY_APP_DATABASE_PASSWORD&quot;] %&gt;</code></pre><p>Replace <code>my_app</code> in the example with the name of your app - in this case <code>tacogram_final</code> would be appropriate. &#xA0;Be sure that the database value still has <code>production</code> appended to it. &#xA0;And <code>MY_APP</code> in the <code>password</code> should be <code>TACOGRAM_FINAL</code> in all caps. &#xA0;As an example, it might look like this:</p><pre><code>production:
  &lt;&lt;: *default
  adapter: postgresql
  database: tacogram_final_production
  username: tacogram_final
  password: &lt;%= ENV[&quot;TACOGRAM_FINAL_DATABASE_PASSWORD&quot;] %&gt;</code></pre><p>Last step is to commit these changes and push them to Github.</p><h3 id="render-setup">RENDER SETUP</h3><p>Sign up for a Render account at <a href="http://render.com/">render.com</a> - use your Github credentials if possible. &#xA0;You may have to enter payment info, but you&apos;ll be using the free tier. &#xA0;During signup, you&apos;ll be asked several questions about how you will use Render - you can answer them however you&apos;d like. &#xA0;Once complete, you&apos;ll be on your Render dashboard.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/render-dashboard.png" class="kg-image" alt loading="lazy" width="2000" height="1248" srcset="https://entr451.com/content/images/size/w600/2024/03/render-dashboard.png 600w, https://entr451.com/content/images/size/w1000/2024/03/render-dashboard.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/render-dashboard.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/render-dashboard.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>You&apos;ll need to create 2 services - one for the database and one for the application. &#xA0; Create the database service first. &#xA0;Click &quot;Create PostgreSQL&quot; on your dashboard (or using the &quot;New +&quot; button in the top right).</p><p>In the form, enter a unique name for your new database - it&apos;s helpful to use your application name followed by &quot;-db&quot; to easily identify it later. &#xA0;For example, it might be <code>tacogram-final-db</code>.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.42.33-PM.png" class="kg-image" alt loading="lazy" width="2000" height="254" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.42.33-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.42.33-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.42.33-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-12.42.33-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Ignore the other settings in the form until the Instance Type at the bottom. &#xA0;Select the &quot;Free&quot; type and click &quot;Create Database&quot;. &#xA0;Note: Render only permits 1 free database service. &#xA0;When you want to create a database for another application, you&apos;ll first need to suspend/delete this one (or upgrade to a paid plan).</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.42.44-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1123" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.42.44-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.42.44-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.42.44-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-12.42.44-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>On the next screen, wait for the database service to complete its setup. &#xA0;Once it&apos;s available, scroll to the &quot;Connections&quot; section, find the &quot;Internal Database URL&quot; value and copy it - you&apos;ll need it in the next step.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.48.45-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1242" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.48.45-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.48.45-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.48.45-PM.png 1600w, https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.48.45-PM.png 2112w" sizes="(min-width: 720px) 720px"></figure><p>Next, go back to your dashboard and click the &quot;New +&quot; button in the top right to create a new Web Service.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.49.49-PM.png" class="kg-image" alt loading="lazy" width="2000" height="689" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.49.49-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.49.49-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.49.49-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-12.49.49-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>When asked &quot;How would you like to deploy your web service?&quot;, choose &quot;Build and deploy from a Git repository&quot;. &#xA0;On the next screen, you&apos;ll be asked to &quot;Connect a repository&quot; - you should see your Github repository listed, but if not, click &quot;Configure account&quot; on the right to authorize access to your Github account. &#xA0;Once you&apos;ve found your application&apos;s repository in the list, click &quot;Connect&quot;.</p><p>In the following form, the &quot;Name&quot; field will be pre-populated for you based on the name of your application. &#xA0;However, this must be unique across all of Render, so you may need to change the name slightly (e.g. <code>tacogram-final</code> may already be taken). &#xA0;You can leave the next few settings unchanged. &#xA0;Then, in the &quot;Build Command&quot; field, enter the 2 commands that should run each time you deploy a new version of your application: <code>bundle install; rails db:migrate</code> (the semi-colon separates the 2 commands).</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.56.01-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1357" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.56.01-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.56.01-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.56.01-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-12.56.01-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Next, change the &quot;Start Command&quot; field. &#xA0;This is the command that will run to start your application server, just like you do in Codespace: <code>rails server</code></p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-12.58.29-PM.png" class="kg-image" alt loading="lazy" width="2000" height="280" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-12.58.29-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-12.58.29-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-12.58.29-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-12.58.29-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Then, choose the free Instance Type option.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-1.00.05-PM.png" class="kg-image" alt loading="lazy" width="2000" height="943" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-1.00.05-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-1.00.05-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-1.00.05-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-1.00.05-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>The last change is adding environment variables needed to run the application - these are variables stored on the &quot;server&quot; instead of in our code and generally used to run the application or for sensitive data we don&apos;t want to store in our code (e.g. API keys). &#xA0;The variables you&apos;ll need to add are:</p><ul><li>DATABASE_URL</li><li>SECRET_KEY_BASE</li><li>WEB_CONCURRENCY</li></ul><p>The <code>DATABASE_URL</code> value should be the &quot;Internal Database URL&quot; value copied from the database service. &#xA0;The <code>SECRET_KEY_BASE</code> value can be anything - your application name will work. &#xA0;And, the <code>WEB_CONCURRENCY</code> value should be <code>2</code>.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-1.02.09-PM.png" class="kg-image" alt loading="lazy" width="2000" height="368" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-1.02.09-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-1.02.09-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-1.02.09-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-1.02.09-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>That&apos;s it. &#xA0;You can click &quot;Create Web Service&quot; to complete the setup. &#xA0;Render will pull your latest code from Github and begin to install all of the necessary libraries. &#xA0;It will automatically run <code>bundle install</code> and <code>rails db:migrate</code>. &#xA0;And then lastly, it will run <code>rails server</code>. &#xA0;If all goes well, you&apos;ll see &quot;Your service is live &#x1F389;&quot; in the logs.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-1.11.50-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1349" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-1.11.50-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-1.11.50-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-1.11.50-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-1.11.50-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>From now on, each time you push new code from Codespace to Github, it will be deployed to Render as well. &#xA0;You can see any deployment events listed in the &quot;Events&quot; tab.</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2024/03/Screenshot-2024-03-06-at-1.19.25-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1116" srcset="https://entr451.com/content/images/size/w600/2024/03/Screenshot-2024-03-06-at-1.19.25-PM.png 600w, https://entr451.com/content/images/size/w1000/2024/03/Screenshot-2024-03-06-at-1.19.25-PM.png 1000w, https://entr451.com/content/images/size/w1600/2024/03/Screenshot-2024-03-06-at-1.19.25-PM.png 1600w, https://entr451.com/content/images/size/w2400/2024/03/Screenshot-2024-03-06-at-1.19.25-PM.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Congratulations! &#xA0;Your app should now be live! &#xA0;There&apos;s a link to visit your live production site towards the top. &#xA0;Note the url for future reference - it will be something like <code>my_app.onrender.com</code> where &quot;my_app&quot; is the unique name you entered previously (e.g. <code>tacogram-final-tues</code>).</p><h3 id="debugging">DEBUGGING</h3><p>Render is really just a big, industrial Linux-based computer &#x2013; just like Codepsace, but more powerful. &#xA0;It&apos;s worth exploring the features of your Render services to discover what&apos;s possible. &#xA0;One feature that&apos;s very useful is access to the logs (see the &quot;Logs&quot; tab just below the &quot;Events&quot; tab in Render. &#xA0;As users interact with your application, you will see the output to the server log there. &#xA0;Just as you did with the rails server log in Codespace, reading these logs will be helpful when you inevitably find a bug in the live app. &#xA0;You won&apos;t see the usual error message that you&apos;ve seen in local development - that would be a bad user experience. &#xA0;Instead you&apos;ll see a simple 404 error screen with no details about what went wrong. &#xA0;</p><figure class="kg-card kg-image-card"><img src="https://entr451.com/content/images/2022/06/Screen-Shot-2022-06-02-at-12.01.20-AM.png" class="kg-image" alt loading="lazy" width="1856" height="534" srcset="https://entr451.com/content/images/size/w600/2022/06/Screen-Shot-2022-06-02-at-12.01.20-AM.png 600w, https://entr451.com/content/images/size/w1000/2022/06/Screen-Shot-2022-06-02-at-12.01.20-AM.png 1000w, https://entr451.com/content/images/size/w1600/2022/06/Screen-Shot-2022-06-02-at-12.01.20-AM.png 1600w, https://entr451.com/content/images/2022/06/Screen-Shot-2022-06-02-at-12.01.20-AM.png 1856w" sizes="(min-width: 720px) 720px"></figure><p>To see the actual error message, you&apos;ll need to look at the server log. &#xA0;Render makes this especially easy by providing search and filter functionality for your logs.</p><h3 id="custom-domains">CUSTOM DOMAINS</h3><p>If you&apos;re interested in adding a custom domain to your application (e.g. <code>tacogram-final.com</code> instead of <code>tacogram-final.onrender.com</code>), you&apos;ll first need to purchase a domain (or use a domain you already own) at a domain registry provider like <a href="https://www.namecheap.com/">Namecheap</a>. &#xA0;Then follow Render&apos;s <a href="https://docs.render.com/custom-domains">instructions</a> to configure the domain to point to your server on Render.</p>]]></content:encoded></item></channel></rss>