{"@attributes":{"version":"2.0"},"channel":{"title":"Kevin Murphy","link":"https:\/\/kevinjmurphy.com\/","description":"Recent content on Kevin Murphy","generator":"Hugo -- gohugo.io","language":"en","copyright":"Copyright &copy; {year} - Kevin Murphy","lastBuildDate":"Sun, 02 Aug 2026 03:33:33 -0400","item":[{"title":"Frequently Played August 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-08\/","pubDate":"Sun, 02 Aug 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-08\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"atlantic-city\">Atlantic City <a href=\"#atlantic-city\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/NpNuYTzk7VQ?si=geyoXgWtqXCTcjAL\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I just got my hands on the Expanded Edition of Nebraska. Easily a top 5 Springsteen album for me. I&rsquo;m not sure the Expanded Edition added much for me though.<\/p><p><a href=\"https:\/\/genius.com\/1495079\">Full Lyrics<\/a><\/p><blockquote><p>Down here, it&rsquo;s just winners and losers<br>And don&rsquo;t get caught on the wrong side of that line<\/p><\/blockquote><h2 id=\"free\">Free <a href=\"#free\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/FpcGoyNKrsI?si=cW_I0uauSPAlGZ65\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This is still, I think, the most important song released this decade to me.<\/p><p><a href=\"https:\/\/genius.com\/25701957\">Full Lyrics<\/a><\/p><blockquote><p>The feeling comes so fast and I cannot control it<br>I&rsquo;m on fire, but I&rsquo;m trying not to show it<\/p><\/blockquote>"},{"title":"ActiveModel Conditional Validations","link":"https:\/\/kevinjmurphy.com\/posts\/activemodel-conditional-validations\/","pubDate":"Mon, 20 Jul 2026 09:00:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/activemodel-conditional-validations\/","description":"<h2 id=\"validating-the-problem-domain\">Validating The Problem Domain <a href=\"#validating-the-problem-domain\">\ud83d\udd17<\/a><\/h2><p>Today we&rsquo;re modeling a system that involves providing validated parking. It&rsquo;s a benefit provided by different businesses. They offer their customers a discount at a nearby parking lot. The lot has the following rules:<\/p><ol><li>All parking validation tickets must have an expiration time when they&rsquo;re issued.<\/li><li>The parking lot owner has a special ticket that does not expire. Instead, the owner provides a PIN after scanning their ticket for free parking.<\/li><li>Please avoid hitting parked cars.<\/li><\/ol><p>The last rule is more of a guideline than anything and won&rsquo;t factor into our modeling.<\/p><p>We will use the <code>ActiveModel::Validations<\/code> <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActiveModel\/Validations.html\">module<\/a> to implement these system restrictions.<\/p><h2 id=\"parking-expiration\">Parking Expiration <a href=\"#parking-expiration\">\ud83d\udd17<\/a><\/h2><p>Parking validation tickets must have an expiration time. We will use a <a href=\"https:\/\/guides.rubyonrails.org\/active_record_validations.html#presence\">presence validation<\/a> to help enforce that.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ValidatedParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:expires_at<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:expires_at<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We will exercise this with a test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is invalid without an expiration timestamp&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> ticket <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">ValidatedParkingTicket<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> ticket<span style=\"color:#f92672\">.<\/span>expires_at <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Time<\/span><span style=\"color:#f92672\">.<\/span>current<\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>By including the <code>ActiveModel::Validations<\/code> module, we gain the ability to set validations. You may have seen these in <code>ActiveRecord<\/code> models. We can also determine if the instance is valid, as well as seeing any errors.<\/p><h2 id=\"owner-pin\">Owner PIN <a href=\"#owner-pin\">\ud83d\udd17<\/a><\/h2><p>Our second rule states that there is a special ticket. One that the owner has with a PIN.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ValidatedParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:owner_pin<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>It&rsquo;s not enough to have the ability to get and set a PIN for the owner&rsquo;s ticket. We need to make sure that the owner&rsquo;s ticket has a PIN. But other kinds of tickets explicitly should not have a PIN. They&rsquo;re not an owner!<\/p><p>You may have seen validations that only apply during certain lifecycle events. Perhaps you&rsquo;ve seen something like a field that only needs to be present when creating an instance. Or another attribute must be greater than one when updating the record.<\/p><p>These conditional validations use the <code>on<\/code> option. And these lifecycle events are contexts within which the validation is applicable.<\/p><p>However, you&rsquo;re not limited to these events. You can define a <a href=\"https:\/\/guides.rubyonrails.org\/active_record_validations.html#on\">custom context<\/a>.<\/p><p>Let&rsquo;s do that to make sure that the PIN is required within the context of an owner.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ValidatedParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:owner_pin<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:owner_pin<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#e6db74\">on<\/span>: <span style=\"color:#e6db74\">:owner<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We must explicitly tell ActiveModel&rsquo;s methods about the context.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;requires a PIN when applying the owner context&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> ticket <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">ValidatedParkingTicket<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?(<span style=\"color:#e6db74\">:owner<\/span>))<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> ticket<span style=\"color:#f92672\">.<\/span>owner_pin <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;1234&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?(<span style=\"color:#e6db74\">:owner<\/span>))<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we don&rsquo;t specify the context, the validation does not apply.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;does not require a PIN without the owner context&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> ticket <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">ValidatedParkingTicket<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">expires_at<\/span>: <span style=\"color:#66d9ef\">Time<\/span><span style=\"color:#f92672\">.<\/span>current)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"owner-expiration\">Owner Expiration <a href=\"#owner-expiration\">\ud83d\udd17<\/a><\/h2><p>Recall that we also require a parking validation to have an expiration timestamp. That doesn&rsquo;t apply for the owner. Their ticket is always valid. As long as they can provide the PIN following swiping their ticket, they don&rsquo;t pay to park.<\/p><p>To accomplish this, we will use another form of conditional validations, the <code>if<\/code> <a href=\"https:\/\/guides.rubyonrails.org\/active_record_validations.html#using-a-proc-with-if-and-unless\">option<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ValidatedParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:expires_at<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:expires_at<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span>: <span style=\"color:#f92672\">-&gt;<\/span> { validation_context <span style=\"color:#f92672\">!=<\/span> <span style=\"color:#e6db74\">:owner<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Note the use of <code>validation_context<\/code>. We&rsquo;re still using contexts here to know whether the expiration applies. This allows us to find out <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActiveModel\/Validations.html#method-i-validation_context\">what context<\/a> we&rsquo;re operating in. If it&rsquo;s not an owner, then we require the expiration timestamp.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is valid without an expiration date when applying the owner context&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> ticket <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">ValidatedParkingTicket<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">owner_pin<\/span>: <span style=\"color:#e6db74\">&#34;1234&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(ticket<span style=\"color:#f92672\">.<\/span>valid?(<span style=\"color:#e6db74\">:owner<\/span>))<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"stay-in-your-spot\">Stay in Your Spot <a href=\"#stay-in-your-spot\">\ud83d\udd17<\/a><\/h2><p>Put together, we have the following setup to model our rules.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ValidatedParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:expires_at<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:expires_at<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span>: <span style=\"color:#f92672\">-&gt;<\/span> { validation_context <span style=\"color:#f92672\">!=<\/span> <span style=\"color:#e6db74\">:owner<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:owner_pin<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:owner_pin<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#e6db74\">on<\/span>: <span style=\"color:#e6db74\">:owner<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>You might notice we have very divergent behavior. Our validations depend on the context we&rsquo;re operating in. We need to remember to apply the context in the appropriate situations. That involves using some <code>ActiveModel<\/code> functionality that may be less familiar to some.<\/p><p>This can lead to situations where our data is unclearly validated. Maybe we forget one time to apply the owner context. This happens when setting up a new ticket for the owner after they lose their original ticket. They&rsquo;ll be coming back soon frustrated when their ticket expired.<\/p><p>We can alternatively avoid these conditional validations entirely. Instead, we can treat these as separate entities. Most of the time, visitors receive these tickets from businesses. They require an expiration date and don&rsquo;t know anything about a PIN.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">VisitorParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:expires_at<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:expires_at<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The owner has a special ticket. It has a PIN and no concept of an expiration timestamp.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LotOwnerParkingTicket<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_accessor<\/span> <span style=\"color:#e6db74\">:pin<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:pin<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>These validations are unambiguous. A <code>LotOwnerParkingTicket<\/code> must have a PIN. The naming of the class itself makes it clear what kind of ticket we&rsquo;re dealing with.<\/p><p>This can also help with behavior that&rsquo;s specific to the types of tickets. Let&rsquo;s say we need to determine if a parking ticket has expired. For a owner ticket, that doesn&rsquo;t apply. It&rsquo;s valid as long as they provide the proper PIN. For the visitor, the ticket expires at a certain time.<\/p><p>We can apply those concepts without needing to intertwine the different cases. We do that with separate classes here.<\/p><h2 id=\"exiting-the-lot\">Exiting the Lot <a href=\"#exiting-the-lot\">\ud83d\udd17<\/a><\/h2><p>There are options to conditionally applying validations to classes. We can specify the context(s) within which certain validations apply. We can use <code>if<\/code> (and its counterpart <code>unless<\/code>) to provide limitations on when the validation takes place.<\/p><p>When you&rsquo;re reaching for these types of validations, stop for a moment. Consider if there&rsquo;s a different modeling approach that&rsquo;ll apply the validations. This does not mean to never use this functionality. They&rsquo;re valid and have their place. When introducing them, consider it an opportunity to revisit your approach. Think if there&rsquo;s another way to organize your rules.<\/p><p>And watch out for parked cars.<\/p>"},{"title":"Frequently Played July 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-07\/","pubDate":"Mon, 20 Jul 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-07\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"hello-kitty-kat\">Hello Kitty Kat <a href=\"#hello-kitty-kat\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/kFW2yFQbcgQ?si=ZABbxGEGEaIzAFpf\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The height of passive-aggressive angst?<\/p><p><a href=\"https:\/\/genius.com\/The-smashing-pumpkins-hello-kitty-kat-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>You know I hate to say it<br>Oh no, I always stay<br>I don&rsquo;t wanna be like the others, please<\/p><\/blockquote><h2 id=\"kittys-back\">Kitty&rsquo;s Back <a href=\"#kittys-back\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/RWRlqFywXTY?si=vtJeYg96JootRYmt\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This is perhaps the <a href=\"https:\/\/www.youtube.com\/watch?v=JtvGR8UX1L0\">Jazz Odyssey<\/a> of Springsteen&rsquo;s catalog, but&hellip;good?<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-kittys-back-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>You better learn to move fast when you&rsquo;re young, or you&rsquo;re not long around<br>Cat somehow lost his Kitty down in the city pound<\/p><\/blockquote>"},{"title":"Frequently Played June 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-06\/","pubDate":"Mon, 01 Jun 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-06\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"not-bad-for-new-jersey\">Not Bad For New Jersey <a href=\"#not-bad-for-new-jersey\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/1uyTONAlPm8?si=KN-aZ6hrxJZkWZKg\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>A <a href=\"https:\/\/www.youtube.com\/watch?v=gHun-LmiWfo\">signature Tele<\/a> AND <a href=\"https:\/\/www.youtube.com\/watch?v=r6MgpNaEL1s\">two new tunes<\/a> from Brian? Yes, please.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-not-bad-for-new-jersey-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I can&rsquo;t say I had a good time<br>But after all this crazy, I think we did alright<\/p><\/blockquote><h2 id=\"make-you-feel-my-love\">Make You Feel My Love <a href=\"#make-you-feel-my-love\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/OiLBVGMx4b4?si=rozBm-tLbqVj6kF0\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Thanks to K-Pop Demon Hunters, my daughter assumes any song lyrics she doesn&rsquo;t understand are in Korean. But sometimes I get to say no, that&rsquo;s just Bob Dylan singing.<\/p><p><a href=\"https:\/\/genius.com\/7695758\">Full Lyrics<\/a><\/p><blockquote><p>The storms are raging on the rolling sea<br>And on the highway of regret<br>The winds of change are blowing wild and free<br>You ain&rsquo;t seen nothing like me yet<\/p><\/blockquote>"},{"title":"Blue Ridge Ruby 2026 Video","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2026-video\/","pubDate":"Sun, 24 May 2026 08:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2026-video\/","description":"<iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/OZZh0ObEdcg?si=dsldPK8ZVQIlncrL\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The videos from Blue Ridge Ruby 2026 are now available on <a href=\"https:\/\/www.youtube.com\/@BlueRidgeRuby\/videos\">YouTube<\/a>. Check them all out!<\/p>"},{"title":"Blue Ridge Ruby 2026 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2026-recap\/","pubDate":"Tue, 05 May 2026 15:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2026-recap\/","description":"<h2 id=\"blue-ridge-ruby-2026\">Blue Ridge Ruby 2026 <a href=\"#blue-ridge-ruby-2026\">\ud83d\udd17<\/a><\/h2><p>I returned to Asheville for the first time since <a href=\"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023-recap\/\">2023<\/a>. Both times for the Blue Ridge Ruby conference. Both times convinced for the same reason: organizer Jeremy Smith. Both times because I was selected as a speaker.<\/p><p>This post is not intended as a review of any of the talks, but to highlight the variety of great work from all involved. I hope you\u2019ll seek out the full videos of all the sessions that interest you once they are available.<\/p><h2 id=\"preparing\">Preparing <a href=\"#preparing\">\ud83d\udd17<\/a><\/h2><p>Jeremy Smith conducted interviews with many of the speakers prior to the conference. Jeremy and I <a href=\"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-notes\/\">chatted<\/a> about various topics related to conferences and writing. Beyond that, I mostly followed <a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">my own advice<\/a>.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><h3 id=\"learning-from-permaculture-sustainable-software-development\">Learning from Permaculture: Sustainable Software Development <a href=\"#learning-from-permaculture-sustainable-software-development\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/john-athayde\/\">John Athayde<\/a> shared hisknowledge of permaculture and how to apply that to our work. I particularlyenjoyed the perspective to allow for a growing season to appreciate andunderstand what already exists before making sweeping changes.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_john.jpg\" alt=\"John at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"state-is-the-first-decision-you-never-made\">State is the First Decision You Never Made <a href=\"#state-is-the-first-decision-you-never-made\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/joel-quenneville\/\">Jo\u00ebl Quenneville<\/a> brokethe internet and then made it work in time for Amazoff&rsquo;s big sale. He broughthis regular, keen insight and sweeping analogies to this talk, as expected.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_joel.jpg\" alt=\"Jo\u00ebl at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"yes--rubys-secret-talent-for-improvisation\">Yes, &amp;\u2026: Ruby\u2019s Secret Talent for Improvisation <a href=\"#yes--rubys-secret-talent-for-improvisation\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/ifat-ribon\/\">Ifat Ribon<\/a> may have just thetalk for you if you&rsquo;re looking for a distinction between procs and lambdas thatwill actually stick. Procs are a loose improviser and lambdas are a by-the-bookplayer.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_ifat.jpg\" alt=\"Ifat at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"how-to-finish-what-you-start-lessons-from-actually-shipping-a-big-refactor\">How To Finish What You Start: Lessons From Actually Shipping a Big Refactor <a href=\"#how-to-finish-what-you-start-lessons-from-actually-shipping-a-big-refactor\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/annie-kiley\/\">Annie Kiley<\/a> shared theexperience of their team successfully landing a big refactor. Remember:<\/p><ul><li>The code isn&rsquo;t the hard part.<\/li><li>If you don&rsquo;t know how you are going to ship it, you probably won&rsquo;t.<\/li><li>Break it up into small, shippable pieces.<\/li><li>Don&rsquo;t get distracted.<\/li><li>Don&rsquo;t go it alone.<\/li><\/ul><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_annie.jpg\" alt=\"Annie at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"instillment-of-successful-practices-in-an-agentic-world\">InstiLLMent of Successful Practices in an Agentic World <a href=\"#instillment-of-successful-practices-in-an-agentic-world\">\ud83d\udd17<\/a><\/h3><p>Thanks to everyone for joining <a href=\"https:\/\/blueridgeruby.com\/speakers\/kevin-murphy\/\">me<\/a> to wrap up the day.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_kevin.jpg\" alt=\"Kevin at Blue Ridge Ruby 2026\"><\/figure>\ud83d\udcf8 Credit: Rachael Wright-Munn<\/p><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><h3 id=\"terminus-a-hanami--htmx-web-application-for-e-ink-devices\">Terminus: A Hanami + htmx web application for e-ink devices <a href=\"#terminus-a-hanami--htmx-web-application-for-e-ink-devices\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/brooke-kuhlmann\/\">Brooke Kuhlmann<\/a> sharedabout Terminus, htmx, ePaper devices, and making them all work together.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_brooke.jpg\" alt=\"Brooke at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"your-first-open-source-contribution\">Your First Open-Source Contribution <a href=\"#your-first-open-source-contribution\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/rachael-wright-munn\/\">Rachael Wright-Munn<\/a>revisited an old favorite for us. Chael has a great vision for the future ofthis talk; I expect this won&rsquo;t be the last time you have an opportunity to seethis.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_rachael.jpg\" alt=\"Rachael at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"llm-telemetry-as-a-first-class-rails-concern\">LLM Telemetry as a First-Class Rails Concern <a href=\"#llm-telemetry-as-a-first-class-rails-concern\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/david-paluy\/\">David Paluy<\/a> shared aboutevaluating the correctness of LLM models.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_david.jpg\" alt=\"David at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"lightning-talks\">Lightning Talks <a href=\"#lightning-talks\">\ud83d\udd17<\/a><\/h3><p>Congrats to Christopher Hagmann, Katya Sarmiento, Travis Dockter, JoshuaWenning, Joshua Paine, Brooke Kuhlmann, Ernesto Tagwerker, David Paluy, DavidHill, and John P Ashenfelter on their talks.<\/p><h3 id=\"optimize-your-mindset-without-overclocking\">Optimize Your Mindset (Without Overclocking) <a href=\"#optimize-your-mindset-without-overclocking\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/christine-seeman\/\">Christine Seeman<\/a>presented a series of strategies for applying mindfulness, habit building,growth mindset, and deep work into your life.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_christine.jpg\" alt=\"Christine at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"5-ways-to-invest-in-yourself-for-the-long-haul\">5 ways to invest in yourself for the long haul <a href=\"#5-ways-to-invest-in-yourself-for-the-long-haul\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/thomas-cannon\/\">Thomas Cannon<\/a> shared hisapproach to building up a history of growth over time.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_thomas.jpg\" alt=\"Thomas at Blue Ridge Ruby 2026\"><\/figure><h3 id=\"closing\">Closing <a href=\"#closing\">\ud83d\udd17<\/a><\/h3><p>Jeremy and team did it again.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_2026_jeremy.jpg\" alt=\"Jeremy at Blue Ridge Ruby 2026\"><\/figure>"},{"title":"Frequently Played May 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-05\/","pubDate":"Tue, 05 May 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-05\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"as-alive-as-you-need-me-to-be\">As Alive As You Need Me To Be <a href=\"#as-alive-as-you-need-me-to-be\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/Hul0_VpIg4s?si=g364AxC0PVH0jN0Q\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Dive into <a href=\"https:\/\/nineinchnails.lnk.to\/nineinchnoize\">the album<\/a>. The NIN x Boys Noize collaboration builds on their work together on the B stage during the Peel It Back tour.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-as-alive-as-you-need-me-to-be-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I never had a choice, connection<br>I never had a chance to catch my breath<\/p><\/blockquote><h2 id=\"one-of-the-greats\">One Of The Greats <a href=\"#one-of-the-greats\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/uFOGGsvWIHU?si=wQfuZKByvFDQTKZw\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The dirty riff, pointed lyrics, and stilted delivery are a fantastic combination.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-one-of-the-greats-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Did I get it right? Do I win the prize?<br>Do you regret bringing me back to life?<br>&hellip;<br>I will let the light in, I will let some love in<br>I will be happy, it will be perfect<\/p><\/blockquote>"},{"title":"InstiLLMent of Successful Practices in an Agentic World","link":"https:\/\/kevinjmurphy.com\/posts\/instillment-of-successful-practices-in-an-agentic-world\/","pubDate":"Tue, 28 Apr 2026 07:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/instillment-of-successful-practices-in-an-agentic-world\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>Congrats on joining Hours Unlimited. The Math and Numbers team is excited to have you join us on our journey to redefine the importance of numerals. This introductory session will provide tips and tricks for best interacting with powerfuLLMachine, the next-level platform we use to unlock productivity and effectiveness.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/OZZh0ObEdcg?si=93sxptMuHtipV7jb\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/instillment-of-successful-practices-in-an-agentic-world\">Slides<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/instillment-of-successful-practices-in-an-agentic-world-proposal\/\">Proposal<\/a><\/li><li><a href=\"https:\/\/youtu.be\/OZZh0ObEdcg?si=EXZ7tjk7-X7eQDaQ\">Video<\/a><\/li><\/ul><h2 id=\"reviews\">Reviews <a href=\"#reviews\">\ud83d\udd17<\/a><\/h2><ul><li><a href=\"https:\/\/ruby.social\/@ChaelCodes\/116502421538023477\">Rachael Wright-Munn<\/a><\/li><\/ul><blockquote><p>@kevin_j_m&rsquo;s talk is a must-watch! I laughed so hard it hurt, and then almost cried. A satirical review of the industry today, with a poignant plea for the future.<\/p><\/blockquote><ul><li><a href=\"https:\/\/christine-seeman.com\/blue-ridge-ruby-2026\/\">Christine Seeman<\/a><\/li><\/ul><blockquote><p>Kevin Murphy\u2019s talk, InstiLLMent of Successful Practices in an Agentic World, started out funny. A hilarious bit about being a new employee at \u201cHours Unlimited\u201d had the room going. Then it quietly flipped a switch. It moved from talking about agents to talking about humans on your team. Code review isn\u2019t just a quality gate, it\u2019s an invitation to a discussion. Form deep working relationships. Treat the other humans on your team like, well, humans. Lessons that work whether your collaborator is an LLM or a coworker.<\/p><\/blockquote><ul><li><a href=\"https:\/\/www.linkedin.com\/posts\/activity-7465039177789743104-csNe?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAHs_zcB9bZ6lOvE86myXc3Puhs5zC35VNs\">John Athayde<\/a><\/li><\/ul><blockquote><p>His talk, &ldquo;InstiLLMent of Successful Practices in an Agentic World,&rdquo; starts funny and quietly turns into something about how we treat the humans on our team. Worth your time.<\/p><\/blockquote>"},{"title":"Frequently Played April 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-04\/","pubDate":"Sun, 05 Apr 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-04\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"witch-dance\">Witch Dance <a href=\"#witch-dance\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/S8qAOX4QmnA?si=B6btFxweXeYCRNCX\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Grief, perseverance, the search for community, the desire for answers.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-witch-dance-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>And I met every monster from the bar to Broadway<br>And all their violent offers, I just turn them away<br>And your threats and your promises, they don&rsquo;t scare me<br>After all, there&rsquo;s nobody more monstrous than me<\/p><\/blockquote><h2 id=\"opening-of-the-land-of-hope-and-dreams-american-tour\">Opening of The Land Of Hope And Dreams American Tour <a href=\"#opening-of-the-land-of-hope-and-dreams-american-tour\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/G_R8lwvtrzc?si=xwsioQD7LJ-olzzn\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I finally watched <a href=\"https:\/\/en.wikipedia.org\/wiki\/Springsteen:_Deliver_Me_from_Nowhere\">Deliver Me From Nowhere<\/a>. My recommendation? Read the <a href=\"https:\/\/www.bookbub.com\/books\/deliver-me-from-nowhere-by-warren-zanes\">book<\/a>. Or, better, listen to <a href=\"https:\/\/brucespringsteen.net\/albums\/nebraska\/\">Nebraska<\/a>.<\/p><blockquote><p>The mighty E Street Band is here tonight to call upon the righteous power of art, of music, of rock and roll, in dangerous times. We are here in celebration and defense of our American ideals: Democracy, our Constitution, and our sacred American promise.<br>\u200e<br>The America that I love, the America that I&rsquo;ve written about for 50 years, that&rsquo;s been a beacon of hope and liberty around the world is currently in the hands of a corrupt, incompetent, racist, reckless, and treasonous administration.<br>\u200e<br>Tonight, we ask all of you to join with us in choosing hope over fear, democracy over authoritarianism, the rule of law over lawlessnes, ethics over unbridled corruption, resistance over complacency, unity over division, and peace over WAR.<\/p><\/blockquote>"},{"title":"Announcing simplecov-compare","link":"https:\/\/kevinjmurphy.com\/posts\/announcing-simplecov-compare\/","pubDate":"Sat, 14 Mar 2026 03:14:16 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/announcing-simplecov-compare\/","description":"<p>SimpleCov&rsquo;s coverage report will tell the coverage of code at a moment of time. You may want to track differences over time though. That&rsquo;s where the <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\">simplecov-compare<\/a> gem comes in.<\/p><h2 id=\"backstory\">Backstory <a href=\"#backstory\">\ud83d\udd17<\/a><\/h2><p>I have a project at work where I want to evaluate the impact of changes on the application&rsquo;s test coverage. I&rsquo;m using <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\">SimpleCov<\/a> to measure and report the coverage.<\/p><p>There are services that help with this. There&rsquo;s a <a href=\"https:\/\/github.com\/kzkn\/simplecov-resultset-diff-action\">GitHub action<\/a> I could reach for, if we did use GitHub Actions.<\/p><p>But what I had were the results of two test runs. I had the JSON output from the two runs. And I wanted to compare what was different between those two files. No pipelines or external services.<\/p><p>So I somewhat begrudgingly built one. And now <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\">SimpleCov::Compare<\/a> exists.<\/p><h2 id=\"exploring-simplecov\">Exploring SimpleCov <a href=\"#exploring-simplecov\">\ud83d\udd17<\/a><\/h2><p>My initial hope was to use SimpleCov itself to do this as much as possible. For that, I started by looking at the <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov.rb#L86\">collation task<\/a>, because I knew that already takes multiple JSON files and stitches them together. I&rsquo;m not combining them together, more the opposite. But it was my entrypoint to learn how SimpleCov is operating.<\/p><p>The <code>ResultMerger<\/code> <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/result_merger.rb\">class<\/a> is where the majority of the logic lives. The results get <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/result_merger.rb#L106\">combined<\/a>. <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/combine\/files_combiner.rb\">File by file<\/a>, <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/combine\/lines_combiner.rb\">line by line<\/a>, and <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/combine\/branches_combiner.rb\">branch by branch<\/a>.<\/p><p>That helped give me an initial understanding on the process, but again, I want to do the opposite. That said, SimpleCov has the ability to represent the data stored in the JSON files as ruby objects. So I pivoted instead to leverage those classes.<\/p><p>The key class (and spoiler alert, ultimate blocker) is the <code>SourceFile<\/code> <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/source_file.rb\">class<\/a>. Given a filename and coverage information, you can instantiate one of these objects. It&rsquo;ll then tell you the coverage information I need, like how many lines are covered or missing. Awesome!<\/p><p>Except it&rsquo;s not, for me at least. To know the <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/source_file.rb#L48\">covered lines<\/a>, we need to have the lines themselves built up. That seems fair. Building the lines needs access to the source (<a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/source_file.rb#L227\">src<\/a>). <em>That<\/em> requires <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/522dc7d3aee12084a80680dcb014580ed156e988\/lib\/simplecov\/source_file.rb#L181\">opening the file<\/a> by the filename. At which point, I cannot use it for my purposes. Because the filename stored in the JSON resultset is machine-specific.<\/p><p>I ran the coverage on a different machine from the one I want to analyze it on. And even though I have those files on this other computer, they&rsquo;re not in the same directory structure. I&rsquo;m unable to use that because <code>SourceFile<\/code> cannot open the file with the filename provided.<\/p><p>So I set out on my own.<\/p><h2 id=\"development\">Development <a href=\"#development\">\ud83d\udd17<\/a><\/h2><p>While I built from the ground-up, I didn&rsquo;t do so from first principles. I used the structure from SimpleCov to inform the organization of the gem. Where relevant, I comment in the code which SimpleCov class the SimpleCov::Compare class correlates to.<\/p><h3 id=\"representing-data\">Representing Data <a href=\"#representing-data\">\ud83d\udd17<\/a><\/h3><p>I was only concerned with lines coverage. I won&rsquo;t explain in this post how ruby reports coverage results. <a href=\"https:\/\/github.com\/ruby\/ruby\/commit\/0026f644d739efed0d69911b434a1012ad55c393\">I&rsquo;ve<\/a> done that both <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/#lines-coverage\">here<\/a> and <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">elsewhere<\/a> before.<\/p><p>I started from the bottom and worked my way up. I provided a way to represent an <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/file_line.rb\">individual line<\/a> in a file. The line can tell you if it&rsquo;s been covered or not. I then combined those lines for an entire <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/file_result.rb\">file<\/a>, which I called a <code>FileResult<\/code>. Here is where I can calculate the lines coverage for a file. Ruby does not provide that information for you.<\/p><p>I built on that again to accept the <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/result_set.rb\">overall coverage output<\/a> of an individual JSON file, called a <code>ResultSet<\/code>. This keeps a record of the files that the coverage report documents after <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/result_file.rb\">parsing the JSON<\/a>.<\/p><h3 id=\"comparing-results\">Comparing Results <a href=\"#comparing-results\">\ud83d\udd17<\/a><\/h3><p>Now that I had the structure of the data, I could do what I came to do: take two result set files and compare them. I started by comparing <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/file_comparison.rb\">one file to another<\/a>. That comparison needs to know if it&rsquo;s comparing the same file (by filename). It reports on the difference in lines coverage between them.<\/p><p>Comparing <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/result_set_comparison.rb\">two result sets<\/a> builds on that. That iterates through every file in the <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/result_set_comparison.rb#L57\">starting result set<\/a> and finds the matching file from the other result set, if there is one. If there is a difference in lines coverage, the class stores that difference.<\/p><p>The comparison sweeps again, this time retrieving the files in the <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/result_set_comparison.rb#L71\">second result set<\/a> that didn&rsquo;t match. These would be new files, or ones that don&rsquo;t exist in the original result set. Those are also added as differences.<\/p><p>Now I know everything that&rsquo;s different between the two coverage runs.<\/p><h3 id=\"displaying-differences\">Displaying Differences <a href=\"#displaying-differences\">\ud83d\udd17<\/a><\/h3><p>Having all that information is great. It&rsquo;s less helpful with no way to see it though. For this I have two different concepts.<\/p><p>A formatter accepts a comparison of result sets. It organizes that data in some readable format. It&rsquo;s expected to respond to a <code>format<\/code> method which returns a string. SimpleCov::Compare ships with one formatter, providing a <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/formatter\/markdown_formatter.rb\">markdown<\/a> summation of the results.<\/p><p>A reporter takes in output to provide. Responding to the <code>report<\/code> method, it&hellip;reports that output in some format. SimpleCov::Compare has two reporters. One for <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/reporter\/simple_reporter.rb\">STDOUT<\/a> and a more \u2728<a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare\/reporter\/glamour_reporter.rb\">glamorous<\/a>\u2728 option.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/simplecov-compare-terminal.png\" alt=\"The output from simplecov-compare as seen in the terminal\"><\/figure><h3 id=\"using-tooling\">Using Tooling <a href=\"#using-tooling\">\ud83d\udd17<\/a><\/h3><p>To access any of this, there is <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/lib\/simplecov\/compare.rb#L22\">one method<\/a> in the top-level API. It accepts file paths to two coverage reports. You can optionally specify the formatter and reporter.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SimpleCov<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Compare<\/span><span style=\"color:#f92672\">.<\/span>report(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">base_path<\/span>: <span style=\"color:#e6db74\">&#34;starting.json&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">to_path<\/span>: <span style=\"color:#e6db74\">&#34;ending.json&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span>)<\/span><\/span><\/code><\/pre><\/div><p>A <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/b8b2a2afd3b663dde64f4bb8eecb69cf8cdd544e\/bin\/simplecov-compare\">script<\/a> also exists to allow you to call this directly from the terminal.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>simplecov-compare starting.json ending.json<\/span><\/span><\/code><\/pre><\/div><h2 id=\"dependencies\">Dependencies <a href=\"#dependencies\">\ud83d\udd17<\/a><\/h2><p>Perhaps ironically, the gem doesn&rsquo;t need SimpleCov directly. The only dependency needed to run the library code is <a href=\"https:\/\/github.com\/marcoroth\/glamour-ruby\">Glamour<\/a> from <a href=\"https:\/\/marcoroth.dev\/\">Marco Roth<\/a>. That gives me the pretty output for my Markdown in the terminal.<\/p><p>The gem uses <a href=\"https:\/\/github.com\/minitest\/minitest\">minitest<\/a> as a test runner with <a href=\"https:\/\/github.com\/testdouble\/mocktail\">mocktail<\/a> for mocking. No disrespect to <a href=\"https:\/\/rspec.info\/\">RSpec<\/a>. I used building this gem as an opportunity to revisit a different set of tools from that which I use day to day at work.<\/p><h2 id=\"the-future\">The Future <a href=\"#the-future\">\ud83d\udd17<\/a><\/h2><p>Honestly? I don&rsquo;t know. As-is, this solves my problem.<\/p><p>Given that it does not rely on SimpleCov, I thought about making this tool-agnostic. I can extract the core that compares coverage results. Each coverage tool would require an adapter. That layer would translate the coverage report into a <code>ResultSet<\/code>. SimpleCov would be the first tool supported. And I&rsquo;d need a different name. If there&rsquo;s interest, <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/issues\">let me know<\/a> along with what types of tools you&rsquo;d like to use this with.<\/p><p>As it stands, the gem is modular enough to support people providing their own <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/tree\/main\/lib\/simplecov\/compare\/formatter\">formatter<\/a> to organize the data. Same for writing their own <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/tree\/main\/lib\/simplecov\/compare\/reporter\">reporter<\/a> to output the results. The <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/main\/bin\/simplecov-compare\">CLI<\/a> will need some work to support that, but it&rsquo;s possible to <a href=\"https:\/\/github.com\/kevin-j-m\/simplecov-compare\/blob\/2402324905668a732302172ab519866121c8a6f6\/lib\/simplecov\/compare.rb#L22\">pass in your own<\/a> through ruby code now.<\/p><p>I&rsquo;ll field any bug reports. I&rsquo;m not sure about feature enhancements. If you give it a try, I&rsquo;d like to hear about it. Enjoying comparing your coverage over time!<\/p>"},{"title":"Frequently Played March 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-03\/","pubDate":"Fri, 06 Mar 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-03\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"everybody-scream\">Everybody Scream <a href=\"#everybody-scream\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/03iBgkXb1EE?si=3pAFfGWK4DJjVhdu\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I&rsquo;m very much looking forward to seeing the most powerful and beautiful singing voice I&rsquo;ve ever heard in concert next month.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-everybody-scream-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>The witchcraft, the medicine, the spells and the injections<br>The harvest, the needle, protect me from evil<br>The magic and the misery, madness and the mystery<br>Oh, what has it done to me?<\/p><\/blockquote><h2 id=\"buckle\">Buckle <a href=\"#buckle\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/xXQmje2bjWc?si=hwUCOpQBWS-NUKIB\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Yes, this was featured recently. I&rsquo;m still listening to it.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-buckle-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I wanna call you on the telephone<br>I made a thousand people love me, now I&rsquo;m all alone<br>And my resolve is sinking like a stone<br>What would I even say?<br>I guess it&rsquo;s something that just never goes away<\/p><\/blockquote>"},{"title":"Ridge Notes","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-notes\/","pubDate":"Thu, 05 Mar 2026 17:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-notes\/","description":"<iframe width=\"388\" height=\"690\" src=\"https:\/\/www.youtube.com\/embed\/dlo53Y_kXdM\" title=\"Jeremy interviews Kevin Murphy\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I caught up briefly with Blue Ridge Ruby organizer Jeremy Smith to talk about speaking, blogging, conferences, and why you should come to Blue Ridge Ruby.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/1HErYpQvzOw?si=ozOOxGmlYzrU8tql\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>"},{"title":"Building a Guitar Stand","link":"https:\/\/kevinjmurphy.com\/posts\/building-guitar-stand\/","pubDate":"Sun, 22 Feb 2026 17:00:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/building-guitar-stand\/","description":"<p>My daughter got me a new guitar stand I have set up in the living room. This lets me <em>see<\/em> my guitars more easily, which I enjoy. Inspired by that, I wanted to make my own stand to support two guitars in the office area of my bedroom.<\/p><h2 id=\"build\">Build <a href=\"#build\">\ud83d\udd17<\/a><\/h2><p>I used a piece of hickory wood that&rsquo;s been waiting to be put to good use for this project. Armed with an idea of the visual look I wanted, I drew the shape I wanted to end up with. I used my pull saw to make the initial cuts, with the hickory secured in a vice.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/template_pull_saw.jpg\" alt=\"Block of wood in a vice marked with pencil and a pull saw starting to cut into it\"><\/figure><p>With my vertical lines cut, I need to connect them horizontally. I drilled a hole with a forstner bit in the corners with a wide enough diameter to use a jigsaw to connect the lines.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/forstner_bit.jpg\" alt=\"A drill with a forstner bit attached ready to drill a hole in a block of wood\"><\/figure><p>I cleaned up those surfaces with sandpaper wrapping a wood block smaller than the cavities I created. They were too small for my orbital sander to reach in.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/sanding_corners.jpg\" alt=\"Sandpaper wrapped around wood inside a wooden joint\"><\/figure><p>With this I had my side structures built. But I needed two sides, and this was one block of wood. I cut the structure in half for two equal sides. I used my tablesaw for this.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/split_in_half.jpg\" alt=\"Two sides from cutting the block in half\"><\/figure><p>To connect the sides together, I cut 1 inch square stretchers for the front and back. I cut dados in the sides of the same width as the stretchers. I attached the sides to the stretchers with wood glue, the stretchers nestled in the dado joints.<\/p><p>I took no pictures of this of course.<\/p><h2 id=\"finish\">Finish <a href=\"#finish\">\ud83d\udd17<\/a><\/h2><p>With that dry, I attached rubber pads along all the areas a guitar might touch. I also put some on the bottom for feet. I finished with polycrylic, because that&rsquo;s what I have hanging around.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/poly.jpg\" alt=\"Two guitar stands drying between coats of finish. Both have my initials and the year signed on them\"><\/figure><p>You can see I built a single-guitar prototype out of cheaper scrap wood. That is less sturdy because there&rsquo;s not the heft to hold a guitar, so it&rsquo;s very prone to falling over. The prototype served its purpose in the development phase though. And it works now as a secondary low-profile stand I can use as long as I have a place where the neck of the guitar can rest.<\/p><h2 id=\"display\">Display <a href=\"#display\">\ud83d\udd17<\/a><\/h2><p>I enjoyed building this project and it&rsquo;s been working as intended.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/finished_stand_side.jpg\" alt=\"The finished stand with two guitars in it pictured from the side\"><\/figure><p>I did need to make some adjustments to the front stand. It was too deep to accommodate some guitars. The knob layouts would hit against the cavity that holds them. I cut down the depth by chopping off some of the top and now we&rsquo;re in business.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/guitar_stand\/finished_stand_front.jpg\" alt=\"The finished stand with two guitars in it pictured from the front\"><\/figure><p>You may recognize the guitars if you read my last post. I recently <a href=\"https:\/\/kevinjmurphy.com\/posts\/installing-guitar-pick-me-ups\/\">did some work<\/a> on them.<\/p>"},{"title":"Installing Guitar Pick(me)ups","link":"https:\/\/kevinjmurphy.com\/posts\/installing-guitar-pick-me-ups\/","pubDate":"Sun, 22 Feb 2026 16:30:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/installing-guitar-pick-me-ups\/","description":"<p>It&rsquo;s 2026. Everything&rsquo;s awful. The only news you hear is bad news. I figured I&rsquo;d spend a few hours trying to make some things sound a little better. So I installed new pickups in some of my guitars.<\/p><h2 id=\"single-coils\">Single Coils <a href=\"#single-coils\">\ud83d\udd17<\/a><\/h2><p>I recently came into a Squier Affinity strat. I would confidently describe the stock instrument as &ldquo;fine&rdquo;. Particularly for an entry-level guitar. It&rsquo;s not even Classic Vibe level! The body is thinner, but that makes it interestingly light. The neck profile isn&rsquo;t to my liking, but the fretwork is better than I&rsquo;ve seen on much more expensive guitars. The tuning machines aren&rsquo;t great, but I like the vintage style.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_start.jpg\" alt=\"A Squier Affinity Stratocaster guitar in Lake Placid Blue finish with a black pickguard and black hardware\"><\/figure><p>All that aside, I got it for one reason.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/billy_corgan_ghost_strat.webp\" alt=\"Billy Corgan playing his Ghost Strat\"><\/figure><p>It&rsquo;s a really good starting point for a tribute to the Ghost Strat. I&rsquo;m not going to add the stickers (though <a href=\"https:\/\/www.ebay.com\/itm\/364256979230?var&amp;widget_ver=artemis&amp;media=COPY\">replicas<\/a> exist). I&rsquo;m also not using Billy&rsquo;s custom set of DiMarzio rail pickups. Of course, he now plays Reverends with custom Railhammer humbuckers.<\/p><p>The stock pickups I&rsquo;d also describe as &ldquo;fine&rdquo;. But, it&rsquo;s not what I want.<\/p><p>I went with the Lace Sensor Ultimate Triple Set (red\/silver\/blue). That&rsquo;s not only because I was going for a Siamese Dream era sound. Even though this is heavily influenced by a specific instrument, I want this to be a versatile player. And lastly, I just wanted to try Lace Sensors.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/lace_sensor_pickups.jpg\" alt=\"The box containing Lace Sensor Ultimate Triple Set\"><\/figure><p>Disaster immediately struck. I had a white switch tip to replace the stock black one, matching the Ghost Strat. This is essential to the tone, much like the <a href=\"https:\/\/guitar.com\/news\/music-news\/paint-colour-actually-changes-the-sound-of-a-guitar-billy-corgan\/\">color of the guitar<\/a>. Unfortunately, I had a US-sized switch tip. The Squier is built overseas, and those use different size switches. I knew that, and yet I still wasn&rsquo;t prepared. I did get a new switch tip, and now we&rsquo;re safely using a white tip.<\/p><p>Step one of installing new pickups is removing the current pickups. For a strat-style guitar, that means unscrewing the pickguard. The pickups screw into the guard. After doing that, I could see that the guitar is routed to support a full humbucker in both the bridge and the neck. I guess that&rsquo;s good to know.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_pickguard_off.jpg\" alt=\"The strat with the pickguard off\"><\/figure><p>The stock pickups had no markings I could see. Before I removed them, I took a Sharpie and labeled which pickup goes to which location. That way if I want to reinstall them or use them for something else, I know where to put them.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_label_stock_pickups.jpg\" alt=\"Labeling black pickups with a black Sharpie - subtle!\"><\/figure><p>I then removed the stock pickups and screwed the Lace sensor pickups into the pickguard. I labeled those wires to help me know which wires went to which pickups.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_label_lace_wires.jpg\" alt=\"Using tape to label the pickup wires 1\/3\/5 to represent each of the pickups\"><\/figure><p>After that, I heated up my soldering iron and it was a paint-by-numbers situation. I followed the wiring diagram from Lace.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_solder_to_switch.jpg\" alt=\"Soldering wires to the switch\"><\/figure><p>As a side note, I have a set of helping hands, but I always end up frustrated with using it. I find I can never get it in just the right place or applying just the right amount of tension. Me being bad at accepting a helping hand is maybe a metaphor for my life. But I don&rsquo;t have the time to ponder life&rsquo;s great mysteries, I have terrible soldering to do!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_lace_loaded.jpg\" alt=\"All Lace sensor pickups installed in the pickguard\"><\/figure><p>I could have, and should have, cut down the lead wires to have a cleaner install. However, that makes me incredibly nervous. I fear that I&rsquo;ll cut them to size, screw up my soldering, need to strip back more wire, and then not be able to reach. Could I have cut them down a <strong>little<\/strong>? Yes. But instead, I did nothing.<\/p><p>Next comes the ever-important tap test. I need to know if any of this works. I plugged the output jack in to a portable amp and cranked the volume. As I moved the pickup selector switch to each position, I tap on each of the pickup magnets with a screwdriver. I make sure that the pickups that should make noise in each position make noise. And the ones that should not make noise don&rsquo;t make noise.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/strat_tap_test.jpg\" alt=\"Tapping the pickup with a screwdriver to hear it make noise to know it&#39;s connected\"><\/figure><p>I had to go back in and resolder the middle pickup, as my solder joint to the switch wasn&rsquo;t making a connection. Another tap test demonstrated I was ready to move on. I reinstalled the pickguard then set the height of the pickups per Lace&rsquo;s instructions. Job done.<\/p><h2 id=\"humbuckers\">Humbuckers <a href=\"#humbuckers\">\ud83d\udd17<\/a><\/h2><p>I have another guitar that&rsquo;s been around for a <a href=\"https:\/\/kevinjmurphy.com\/posts\/scraping-buy\/\">while<\/a>. The guitar itself I enjoy (especially for the price). But I&rsquo;ve not liked the pickups. They sound really muddy easily when I&rsquo;d prefer clarity. I&rsquo;ve played with pickup height and various other changes to my rig to accommodate it. I just haven&rsquo;t gotten along with the sound of the electrical signal coming out of the instrument.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_start.jpg\" alt=\"A gold Firefly FFLP sitting on a table, ready to be worked on\"><\/figure><p>I aimed to rectify that with a set of Tonerider Alnico 2 Classic humbuckers.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/tonerider_pickups.jpg\" alt=\"Tonerider Alnico 2 Classic humbucker boxes\"><\/figure><p>I also replaced the wiring harness. What was in there before had some sort of quick-connect system. That meant I need to take out the existing pots. Those are threaded through the body. So I needed to pop the knobs off, which I do by weaving a towel underneath and pulling up.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_knobs_off.jpg\" alt=\"Placing a towel under a knob to pull up and remove the knob\"><\/figure><p>After unscrewing the nuts under the knobs, I flipped the guitar over. I removed the cavity cover and started removing the existing electronics.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_guts_out.jpg\" alt=\"The back of the FFLP, with the wires, switch, and pots exposed\"><\/figure><p>With that done, I flipped the guitar back over to unscrew the pickups from the body. I started with the back and then turned back over so that I could disconnect the pickups from the pots. Then when I unscrewed the pickups, I could snake the wire out from the internal routes.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_pickups_out.jpg\" alt=\"A humbucker pickup resting on the body of the FFLP\"><\/figure><p>I installed the new switch first, as those wires have the longest distance to travel through the same routes as the pickups. Following that, I put the new volume and tone pots in the proper location. I secured them lightly with a nut on the top of the body of the guitar. Then I screwed the neck pickup into the pickup rings and routed the wires through the body. I did the same for the bridge.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_before_solder.jpg\" alt=\"All the wiring in the body cavity, ready for solder\"><\/figure><p>Then it was back to soldering poorly, again following the diagram from the manufacturer. I set this up to use <a href=\"https:\/\/obsidianwire.com\/blogs\/the-great-guitar-natter\/les-paul-50s-wiring-vs-modern-60s\">50&rsquo;s wiring<\/a>, for an easier volume roll-off.<\/p><p>Here&rsquo;s the mess hiding in the body cavity post-soldering.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_soldered.jpg\" alt=\"Switch, pickups, output jack, and pots wired together\"><\/figure><p>I had more work to do before cleaning up and declaring victory. Another tap test.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pickups\/fflp_tap_test.jpg\" alt=\"A screwdriver tapping the pickup to check for signal\"><\/figure><p>Note that I also replaced the output jack, so that&rsquo;s why that&rsquo;s hanging out of the back cavity. No use installing it where it belongs until I knew it worked. That was where I started for the reassembly. From there I closed up the back cavity and flipped the guitar over to secure all the pickup screws and pots.<\/p><p>I once again used the pickup manufacturer&rsquo;s suggestion for the height of the pickups. That&rsquo;s where they remain now.<\/p><h2 id=\"verdict\">Verdict <a href=\"#verdict\">\ud83d\udd17<\/a><\/h2><p>I <em>like<\/em> the Lace Sensors, but I&rsquo;m surprised by them. I think most of this is honestly just ignorance on my part. I&rsquo;ve never played them before. I&rsquo;ve only heard people playing them. I thought they&rsquo;d be quieter than they are. I also don&rsquo;t like the in-between positions. But I do find myself using the middle pickup on its own (gasp) for some clean tones. Not even funk, which I can&rsquo;t play, and seems to be all anyone does when they demonstrate a strat middle pickup. The bridge crushes as expected, and the neck works (to me) as a strat neck pickup.<\/p><p>The Tonerider humbuckers are really great. I find them to be very expressive and responsive. They don&rsquo;t get unexpectedly muddy under gain. They clean up great, which is important to me. I like playing with some gain and being able to roll back my volume on my guitar. That way I have a crunch sound and can get a nice-enough clean without having to change any other settings in my rig.<\/p><p>Ultimately, I&rsquo;m still bad at playing guitar and somehow worse at soldering. But I still enjoyed the process, and I&rsquo;m enjoying playing them. So, I&rsquo;m making bad noise alongside all the bad news. But I&rsquo;m ok with making noise poorly with some poorly-soldered joints. Because it&rsquo;s my bad noise, using my bad solder joints.<\/p>"},{"title":"Speaking at Blue Ridge Ruby 2026","link":"https:\/\/kevinjmurphy.com\/posts\/speaking-at-blue-ridge-ruby-2026\/","pubDate":"Mon, 16 Feb 2026 12:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/speaking-at-blue-ridge-ruby-2026\/","description":"<p>I&rsquo;m thrilled to be part of the <a href=\"https:\/\/blueridgeruby.com\/#speakers\">program<\/a> for Blue Ridge Ruby 2026 in Asheville, North Carolina.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_2026_speakers.png\" alt=\"There are 10 talks at Blue Ridge Ruby 2026\"><\/figure><p>I&rsquo;ll be <a href=\"https:\/\/blueridgeruby.com\/speakers\/kevin-murphy\/\">speaking<\/a> about&hellip;LLMs? I hope you can <a href=\"https:\/\/blueridgeruby.com\/#register\">join us<\/a>!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_2026_abstract.png\" alt=\"My abstract for Blue Ridge Ruby 2026, readable below\"><\/figure><h2 id=\"instillment-of-successful-practices-in-an-agentic-world\">InstiLLMent of Successful Practices in an Agentic World <a href=\"#instillment-of-successful-practices-in-an-agentic-world\">\ud83d\udd17<\/a><\/h2><p>Congrats on joining Hours Unlimited. The Math and Numbers team is excited to have you join us on our journey to redefine the importance of numerals. This introductory session will provide tips and tricks for best interacting with powerfuLLMachine, the next-level platform we use to unlock productivity and effectiveness.<\/p>"},{"title":"Frequently Played February 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-02\/","pubDate":"Sun, 01 Feb 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-02\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"the-weatherman\">The Weatherman <a href=\"#the-weatherman\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/8pLk3bVj6IE?si=dlKq7hrqA4bhDbhu\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Trying to predict the future is almost as bad as my prediction coming true.<\/p><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-the-weatherman-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>And I put aside my strongest desires that ruled me before<br>And I took all the pain that I could find<br>&lsquo;Til I exploded in electric light<br>Now I wander the open skies, waiting on the storm<\/p><\/blockquote><h2 id=\"killing-in-the-name-of\">Killing In The Name Of <a href=\"#killing-in-the-name-of\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/LLMKAxFtjB8?si=35jaoog0PGv_OGgl\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I am of course essentially contractually obligated to point out that his former boss showed up with <a href=\"https:\/\/youtu.be\/FMexHF9LK5I?si=HCFnAplpkUHTv6ht\">something new<\/a> and <a href=\"https:\/\/www.youtube.com\/watch?v=Tj-yWE9fJpg\">something familiar<\/a>.<\/p><blockquote><p>Ain&rsquo;t nobody coming to save us except us. And brothers and sisters, you are showing the way.<\/p><\/blockquote>"},{"title":"How I Read A Pull Request","link":"https:\/\/kevinjmurphy.com\/posts\/how-i-read-a-pr\/","pubDate":"Fri, 16 Jan 2026 20:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/how-i-read-a-pr\/","description":"<p>An <a href=\"https:\/\/bikeshed.thoughtbot.com\/448\">episode<\/a> of the Bike Shed podcast convinced me to dig this out of my drafts. Thanks Stephanie and Jo\u00ebl.<\/p><p>I read a lot of pull requests (PRs) for work. It&rsquo;s been a huge accelerator for my growth. It helps push work towards production. Work that&rsquo;s in a pull request is likely closer to completion than what I&rsquo;m working on. Alternatively, the PR may exist because the author is stuck. That also deserves my attention. I hope my reviews help share knowledge and provoke thought from time to time.<\/p><p>I&rsquo;ve developed a bit of a system to work through it. I&rsquo;m going to share that with some help from the <a href=\"https:\/\/www.thoughtco.com\/journalists-questions-5-ws-and-h-1691205\">reporters&rsquo; questions<\/a>. Note that this is not going to focus on how I comment on changes. Only the mechanic I use to read them.<\/p><h2 id=\"when\">When? <a href=\"#when\">\ud83d\udd17<\/a><\/h2><p>I tend to review pull requests continuously throughout the day. I often react quickly to notifications for review. I have an unhealthy relationship with my inbox, and I don&rsquo;t recommend mimicking it. However, I do have some more reasonable suggestions for times to review a pull request:<\/p><h3 id=\"starting-the-day\">Starting The Day <a href=\"#starting-the-day\">\ud83d\udd17<\/a><\/h3><p>This can help you get some momentum to push you into your own work for the day.<\/p><h3 id=\"between-meetings\">Between Meetings <a href=\"#between-meetings\">\ud83d\udd17<\/a><\/h3><p>Do you have that awkward 22 minutes from when one meeting let out until when your next starts? Maybe you&rsquo;re unlikely to get serious traction on some development of your own. But you could dig into some work ready for review.<\/p><h3 id=\"before-lunch\">Before Lunch <a href=\"#before-lunch\">\ud83d\udd17<\/a><\/h3><p>Gift yourself the treat of a satisfying lunch&hellip;after you review a pull request from a peer.<\/p><h3 id=\"after-lunch\">After Lunch <a href=\"#after-lunch\">\ud83d\udd17<\/a><\/h3><p>Much like the start of your day, this may help you get back into the swing of looking at code.<\/p><h3 id=\"after-you-submit-a-pr\">After You Submit A PR <a href=\"#after-you-submit-a-pr\">\ud83d\udd17<\/a><\/h3><p>You&rsquo;ve just pushed some work into existence that you would like some review on. Help yourself rack up some karma by reviewing someone else&rsquo;s pull request at that time. Now that you&rsquo;ve landed that unit of work to review, you&rsquo;re likely looking to context switch anyway. That makes this a time that is hopefully less disruptive to review other code.<\/p><h3 id=\"ending-the-day\">Ending The Day <a href=\"#ending-the-day\">\ud83d\udd17<\/a><\/h3><p>As long as you&rsquo;re not forced to rush your way through it, this can be a satisfying way to conclude your day. You get a sense of accomplishment and help out a colleague along the way.<\/p><h2 id=\"what\">What? <a href=\"#what\">\ud83d\udd17<\/a><\/h2><p>What is the reason for this pull request to exist? If this is intended to merge to production, then I need to be mindful of ALL THE THINGS that entails.<\/p><p>Is this an early draft where the author is looking for some feedback? Then I want to hone in only on their area of concern. For example, unless they want feedback on tests, I&rsquo;m not going to scrutinize the test coverage. I&rsquo;m not concerned about style at all. Everything surrounding what they want feedback on may help tell the story. But it&rsquo;s off-limits (to me) from providing notes on. Not that everything needs to be <a href=\"https:\/\/youtu.be\/-nqRkAsZumc?si=dS6HKVUW4fhZPF8-\">stellar<\/a> anyway. But especially the bits that aren&rsquo;t what they want to hear about.<\/p><h2 id=\"why\">Why? <a href=\"#why\">\ud83d\udd17<\/a><\/h2><p>I need to check my motivation for reviewing this unit of work. Have I been explicitly asked and tagged as a reviewer? Am I familiar with this area of the code? Do I have some expertise to bring to the table?<\/p><p>Alternatively, am I inserting myself into the situation for my own benefit? Is this a piece of functionality or technology I&rsquo;m looking to learn more about?<\/p><p>This will affect the type of review comments I leave, but also sets the scene for how I read the PR. I may breeze through something that I&rsquo;m purely interested in catching up on how the project is going. In this case I have no intent on leaving a single comment or providing a review. Or I may pore over line after line of something I want to learn more about. I may need to remind myself of something I did days or years prior if it&rsquo;s something I worked on in the past.<\/p><h2 id=\"who\">Who? <a href=\"#who\">\ud83d\udd17<\/a><\/h2><p>If this is someone I know well, I can load up their code style or preferences in my head. I try to recall conversations we&rsquo;ve already had and remember their positions on them. Once it comes time to leave feedback, I don&rsquo;t want to sound like a broken record.<\/p><p>Even if it&rsquo;s a bot pushing a dependency update I need to consider my audience. The author isn&rsquo;t the only audience. Who else is likely to encounter this PR?<\/p><h2 id=\"where\">Where <a href=\"#where\">\ud83d\udd17<\/a><\/h2><p>I organize my focus in a linear fashion that mirrors Github&rsquo;s current tab structure. I start, perhaps predictably, with the title and description. I prefer the description to include information about why this change is being made. If they considered alternatives, having them listed here is great. Should there be areas of concern that require scrutiny, call them out here. Ultimately these are the same things I look for in a commit message. I use this to set the stage for what I read and how.<\/p><p>I try to avoid the existing comments on the PR until just before I submit a review. Unless the reason I&rsquo;m drawn to this pull request is because I was tagged in a specific comment.<\/p><p>I then look briefly at the commits. Even if a PR author recommends to read this commit by commit, I will admit that I rarely do it. I do look at the commits to see if it can raise awareness about any structure or order for how the work came together.<\/p><p>I spend most of my time reviewing the files changed. Surprise!<\/p><h2 id=\"how\">How? <a href=\"#how\">\ud83d\udd17<\/a><\/h2><p>Now that I&rsquo;m looking at the contents of the files changed, I start by&hellip;not really reading the changes. Instead, I scroll from top to bottom. The alphabetical file arrangement is pretty arbitrary for code organization. I&rsquo;m scrolling to get a sense of the general shape. I&rsquo;m reading the file names affected. I&rsquo;m seeing where the bulk of the work exists. I&rsquo;m basically doing the <a href=\"https:\/\/youtu.be\/8bZh5LMaSmE?si=4Tvs7GUqSEsebB-G&amp;t=312\">squint test<\/a> on the changes.<\/p><p>When scrolling through tests, I may read the description and not much else during this pass.<\/p><h3 id=\"test-driven-review\">Test-Driven Review <a href=\"#test-driven-review\">\ud83d\udd17<\/a><\/h3><p>After having scrolled to the bottom, in ruby projects, I now tend to be exactly where I want to be. You see, much like you may write tests first (or you&rsquo;ve heard some people do) I tend to READ tests first. In ruby projects, these tests are going to be in the <code>test<\/code> or <code>spec<\/code> directories. Those are likely towards the bottom of the alphabetical sort.<\/p><p>I read the tests as an indication of where to focus. Areas where there are tests can signal things that the author thought were important. Areas with a lot of tests could mean this is <em>really<\/em> important. It could also mean it&rsquo;s <em>really<\/em> confusing, or was difficult for the author. In which case, they wrote a lot of tests to make sure it works.<\/p><p>This is not a guarantee. Maybe there are no tests for a crucial area of the code. And the reasons for that could be many. By reading the tests first, I&rsquo;ve started to inform my brain about what I expect the implementation to look like. If I find part of the implementation that&rsquo;s a surprise - that&rsquo;s also signal in my reading. Why isn&rsquo;t there a test for this? Did I miss it? Should there be one?<\/p><p>I&rsquo;m not encumbered with knowing the implementation that may justify the test&rsquo;s complexity. If a test has lengthy setup, I have no supporting code to rationalize it. Instead, I see the complicated setup alone. The same goes for heavy mocking and stubbing. None of these things are necessarily barriers to merging. However, I can spot them more easily without the burden of knowledge to explain why they might be there. I can spot it as a potential discussion point. There may be changes we can make to the test. This may highlight potential opportunities for a different design.<\/p><h3 id=\"implementation\">Implementation <a href=\"#implementation\">\ud83d\udd17<\/a><\/h3><p>Once I&rsquo;ve looked at the tests, I return to the implementation in detail. As mentioned, I can use the knowledge from reading the tests to guess generally how the code looks.<\/p><p>I tend to look at the implementation alphabetically. Unless there are tests that seem particularly confusing or interesting to me. Then I may hop right into that area of the implementation to learn more. Otherwise, I read alphabetically from top to bottom. It makes it easier for me to know that I have read everything in detail. I know I can mark files as viewed in Github. For whatever reason, I don&rsquo;t find myself frequently using that feature.<\/p><h3 id=\"big-picture-focus\">Big Picture Focus <a href=\"#big-picture-focus\">\ud83d\udd17<\/a><\/h3><p>Once I&rsquo;ve read all of the changes, I go back to the description to ensure it&rsquo;s meeting the goals outlined there. This is also when I&rsquo;ll look at existing comments and discussion.<\/p><p>If the changes are large enough, I consider taking another scroll. In this pass, I&rsquo;m thinking about the overall structure and organization. Are there idioms or design patterns we should consider? Have we done something similarly in this application or another that we can learn from? These are the questions I&rsquo;m asking myself as I read the code for this last pass.<\/p><h2 id=\"why-not\">Why Not? <a href=\"#why-not\">\ud83d\udd17<\/a><\/h2><p>I will admit, I do not employ this thorough approach every time. Not because I&rsquo;m lazy. Well, not only because I&rsquo;m lazy. Sometimes the change doesn&rsquo;t warrant it. Sometimes the approach doesn&rsquo;t apply.<\/p><p>I may be reviewing a one line change to fix a nasty bug that took days for the author to resolve. That shouldn&rsquo;t require the scrutiny I&rsquo;ve placed here on the files changed. However, I expect to be spending a lot of time reading the description or commits. I need to understand the context for the change. I may be reading documentation or source code of a related dependency. The approach changes based on the needs of the code change itself.<\/p><h2 id=\"how-about-you\">How About You? <a href=\"#how-about-you\">\ud83d\udd17<\/a><\/h2><p>Now that you&rsquo;ve read about my process, consider how you read a code change. Maybe you have a standard approach and you&rsquo;re not even aware of it. Perform some investigative journalism on your own tools and methods that you use. Think about why you do things the way you do and the value it adds. Report back so we can all learn from you!<\/p>"},{"title":"Frequently Played January 2026","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-01\/","pubDate":"Fri, 16 Jan 2026 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2026-01\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"downbound-train\">Downbound Train <a href=\"#downbound-train\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/Q80_LmmJWno?si=yk9l7SHwHfdv4ZQN\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>In a debate about the most underrated Springsteen song, this would be high on my list.<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-downbound-train-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Last night, I heard your voice<br>You were crying, crying, you were so alone<br>You said your love had never died<br>You were waiting for me at home<\/p><\/blockquote><h2 id=\"feel-the-pain\">Feel The Pain <a href=\"#feel-the-pain\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/HtdTKrXrRlQ?si=P0ToSmEal5469LYi\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Although I expect my ears will need a week to readjust, even with hearing protection, I&rsquo;d like to see J Mascis and crew live.<\/p><p><a href=\"https:\/\/genius.com\/Dinosaur-jr-feel-the-pain-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I feel the pain of everyone<br>Then I feel nothing<\/p><\/blockquote>"},{"title":"2025 Year-End Review","link":"https:\/\/kevinjmurphy.com\/posts\/2025-review\/","pubDate":"Wed, 17 Dec 2025 14:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/2025-review\/","description":"<p>This isn&rsquo;t a &ldquo;real&rdquo; post. This is a summary of all the things that made up my year in 2025 (almost all in a professional context). Thanks to all who were a part of it.<\/p><h2 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h2><p>I published 8 articles about Ruby or software development in general this year. The first was about <a href=\"https:\/\/kevinjmurphy.com\/posts\/what-is-it-in-ruby-34\/\">it<\/a>. My last post of the year was a highly-nuanced take on <a href=\"https:\/\/kevinjmurphy.com\/posts\/how-to-review-ai-generated-prs\/\">reviewing AI PRs<\/a>.<\/p><p>In between, I wrote about <a href=\"https:\/\/kevinjmurphy.com\/posts\/dont-rest-on-your-laurels\/\">REST<\/a>, <a href=\"https:\/\/kevinjmurphy.com\/posts\/preserving-flash-messages-in-rails\/\">flash messages<\/a>, and more.<\/p><p>I continue sending my posts to <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newsletter subscribers<\/a>. You can still subscribe via <a href=\"https:\/\/kevinjmurphy.com\/index.xml\">RSS<\/a> or try to keep up wherever I post on <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">social media<\/a>.<\/p><p>My most-read posts are those that end up in <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">other newsletters<\/a>. This year, my writing appeared in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> 6 times and <a href=\"https:\/\/newsletter.shortruby.com\/\">Short Ruby Newsletter<\/a> 6 times.<\/p><p>I kept with my plan to publish a &ldquo;real post&rdquo; every other month. Eventually that&rsquo;ll turn into &ldquo;whenever I want or have something to say&rdquo;. This is the first full year I also shared what <a href=\"https:\/\/kevinjmurphy.com\/tags\/music\/\">songs<\/a> I was stuck on each month.<\/p><h2 id=\"speaking\">Speaking <a href=\"#speaking\">\ud83d\udd17<\/a><\/h2><p>I did submit to one CFP this year and was accepted. It was important to me to <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2025-recap\/\">be at<\/a> and <a href=\"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf\/\">speak at<\/a> the final RailsConf. I came into the event with many complicated feelings. Those feelings have only become more difficult to tease out with what&rsquo;s happened in the community since then.<\/p><p>As with the past few years, I was a coach for the RubyCentral CFP coaching sessions for RailsConf. I helped friends prepare their proposals and talks when asked.<\/p><p>No podcasts this year.<\/p><p>If you have an event you&rsquo;d like me to speak at, or a podcast you&rsquo;d like me to guest on, let me know.<\/p><h2 id=\"employer\">Employer <a href=\"#employer\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve been at <a href=\"https:\/\/www.pubmark.com\/\">Pubmark<\/a> all year. I enjoy the work that I do, and the people I do it with. There have been some shifts and changes, but I&rsquo;ve been trying to stay in my comfort zone. It&rsquo;s been helpful for my personal life.<\/p><h2 id=\"new-to-me-in-2025\">New (to me) in 2025 <a href=\"#new-to-me-in-2025\">\ud83d\udd17<\/a><\/h2><p>Here&rsquo;s where I take a break to step out of work and talk about things (mostly music) I got into this year.<\/p><h3 id=\"the-last-dinner-party\">The Last Dinner Party <a href=\"#the-last-dinner-party\">\ud83d\udd17<\/a><\/h3><p>I caught on to them earlier this year and was treated with a new album later in the year. I&rsquo;m looking forward to seeing them live in 2026.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/jDmQLeyUWZ8?si=6v8E_p8ultToe8ph\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h3 id=\"light-from-uncommon-stars\">Light from Uncommon Stars <a href=\"#light-from-uncommon-stars\">\ud83d\udd17<\/a><\/h3><p>I think this was the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Light_from_Uncommon_Stars\">book<\/a> I enjoyed the most this year, but it&rsquo;s close between that and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Beautyland\">Beautyland<\/a>.<\/p><h3 id=\"yvette-young\">Yvette Young <a href=\"#yvette-young\">\ud83d\udd17<\/a><\/h3><p>I heard about Yvette from the NAMM coverage of this year, which included her signature guitar pedal, the <a href=\"https:\/\/yvetteyoungmusic.com\/products\/signed-qi-etherealizer-pedal-by-walrus-audio\">Qi Etherealizer<\/a>. From there, I dug into a bunch of interviews she&rsquo;s done, and obviously her music. Her technical proficiency is matched by her ability to evoke emotion through music. I also really appreciate how she explains her approach and reasons she makes the choices she does.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/yfDiY4BPpwk?si=QeWBQVgh61EPIkaO\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h2 id=\"thank-you\">Thank You <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>Thanks for reading, watching, or listening. It&rsquo;s been a rough year for the world it seems. I hope you find happy and joy, no matter how fleeting it is.<\/p>"},{"title":"Frequently Played December 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-12\/","pubDate":"Mon, 15 Dec 2025 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-12\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"buckle\">Buckle <a href=\"#buckle\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/xXQmje2bjWc?si=hwUCOpQBWS-NUKIB\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Just get the new album. And all the prior albums. You&rsquo;re welcome.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-buckle-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I&rsquo;m much too old for this<br>But I&rsquo;m not over it<\/p><p>&lsquo;Cause I&rsquo;m stupid and I&rsquo;m damaged and you&rsquo;re a disaster<br>When you walk into the room, oh, none of it matters<br>Oh, baby, I just buckle my resolution in tatters<br>&lsquo;Cause I know it won&rsquo;t work, but make it ache, make it hurt<br>I&rsquo;m not better than this, show me what I&rsquo;m worth<\/p><p>Keep me a secret, choose someone else<br>I&rsquo;ll still be here hanging off, I&rsquo;m hanging off<br>The buckle on your belt<\/p><\/blockquote><h2 id=\"painkillers\">Painkillers <a href=\"#painkillers\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/Pc0wM7AFNWE?si=DHJu4frm7OvZb20e\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Yup, Brian Fallon again. I don&rsquo;t have very varied musical tastes.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-painkillers-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>And we want love like it was a drug<br>Yeah, all we wanted was a little relief<br>And every heart I held in between<br>Well, they were painkillers to me<br>Yeah, they were painkillers to me<br>Come get your painkillers from me<\/p><\/blockquote>"},{"title":"How to review AI Generated PRs","link":"https:\/\/kevinjmurphy.com\/posts\/how-to-review-ai-generated-prs\/","pubDate":"Fri, 12 Dec 2025 13:54:20 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/how-to-review-ai-generated-prs\/","description":"<p><a href=\"https:\/\/docs.github.com\/en\/pull-requests\/collaborating-with-pull-requests\/incorporating-changes-from-a-pull-request\/closing-a-pull-request\">Close them<\/a>.<\/p>"},{"title":"Don't REST on your Laurels","link":"https:\/\/kevinjmurphy.com\/posts\/dont-rest-on-your-laurels\/","pubDate":"Sat, 15 Nov 2025 10:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/dont-rest-on-your-laurels\/","description":"<h2 id=\"restful-routes\">RESTful Routes <a href=\"#restful-routes\">\ud83d\udd17<\/a><\/h2><p>You can find many resources <a href=\"https:\/\/www.rubyevents.org\/talks\/in-relentless-pursuit-of-rest\">trumpeting the benefits<\/a> of following RESTful routes in the context of a <a href=\"https:\/\/www.thegnar.com\/blog\/always-use-restful-routes\">Rails application<\/a>. I want to focus on my personal favorite benefit:<\/p><p>Adherence to the default RESTful actions creates a constraint, and is a noticeable heuristic, that aids in limiting the surface area of classes.<\/p><h2 id=\"planting-an-example\">Planting an example <a href=\"#planting-an-example\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s say we have an application that tracks people&rsquo;s accomplishments. We call each instance of an accomplishment a laurel wreath. We love ourselves a metaphor.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LaurelWreathsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">show<\/span><\/span><\/span><span style=\"display:flex;\"><span> @laurel_wreath <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">LaurelWreath<\/span><span style=\"color:#f92672\">.<\/span>find(params<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:id<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This is one of the <a href=\"https:\/\/guides.rubyonrails.org\/routing.html#crud-verbs-and-actions\">mappings<\/a> of HTTP verb and URL to controller action that Rails expects and provides out of the box.<\/p><h2 id=\"growing-branches\">Growing branches <a href=\"#growing-branches\">\ud83d\udd17<\/a><\/h2><p>We have new functionality we want to support. Users can acknowledge that other people assisted in achieving their accomplishment. This publicly demonstrates the impact others had in a person&rsquo;s growth in the best possible way: by impersonally posting it on a website.<\/p><p>We already have a controller about these accomplishments. Right now it has the ability to display one. We can <a href=\"https:\/\/www.phrases.org.uk\/meanings\/rest-on-his-laurels.html\">build on that success<\/a> by using that controller to wire up this action. It <em>is<\/em> acting on the laurel wreath resource after all. It&rsquo;s a natural fit for it.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LaurelWreathsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">branch_owner<\/span><\/span><\/span><span style=\"display:flex;\"><span> @laurel_wreath <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">LaurelWreath<\/span><span style=\"color:#f92672\">.<\/span>find(params<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:id<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @laurel_wreath<span style=\"color:#f92672\">.<\/span>branches <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>find(params<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:branch_user_id<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> redirect_to laurel_wreath_path(@laurel_wreath)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;re again continuing the metaphor. These people are branches on the tree that make up the wreath.<\/p><blockquote><p>The reason for that association takes us into the myth of Apollo\u2019s love for the nymph Daphne, who turned into a Bay tree just as Apollo approached her (anything could happen if you were a Greek god). Undeterred, Apollo embraced the tree, cut off a branch to wear as a wreath and declared the plant sacred.<\/p><\/blockquote><p>On the routing side, we add <a href=\"https:\/\/guides.rubyonrails.org\/routing.html#adding-more-restful-routes\">another route<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>resources <span style=\"color:#e6db74\">:laurel_wreaths<\/span>, <span style=\"color:#e6db74\">only<\/span>: <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:show<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> member <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> put <span style=\"color:#e6db74\">&#34;branch_owner&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"admiring-our-success\">Admiring our success <a href=\"#admiring-our-success\">\ud83d\udd17<\/a><\/h2><p>This works! It&rsquo;s relatively contained. It feels logically located. It&rsquo;s <em>only<\/em> one additional method. What&rsquo;s the big deal?<\/p><p>I&rsquo;ll grant you that you could argue whether this is RESTful. But what we can hopefully agree on is that this is expanding on the default routes. Rails supports it. So we&rsquo;re good.<\/p><p>Until we aren&rsquo;t. To illustrate what I mean, let&rsquo;s take a little diversion.<\/p><h2 id=\"a-big-trunk\">A big trunk <a href=\"#a-big-trunk\">\ud83d\udd17<\/a><\/h2><p>You may commonly hear that code constructs that are &ldquo;too big&rdquo; are troublesome. Big classes. Big methods. Big numbers of arguments. Big numbers of methods that an object can respond to. The trouble could be for many reasons.<\/p><ol><li>Big things are hard to keep in your head.<\/li><li>Big things make it more difficult to find details, which may obscure bugs.<\/li><li>Big things may rob us of a more declarative, or better understood, abstraction.<\/li><\/ol><p>There are probably more. I wouldn&rsquo;t want to make a list that&rsquo;s too big though.<\/p><p>Even if we do agree that this is undesirable, we have a <em>big<\/em> problem. Each one of us has a different definition of what &ldquo;too big&rdquo; is. One person&rsquo;s long method is another person&rsquo;s explanatory implementation.<\/p><p>How many lines of code is too many in a class? When is a PR too big? When should we decompose something? These are <strong>difficult<\/strong> questions. They have a lot of nuance. They involve a lot of personal biases. It&rsquo;s frustrating to achieve consensus here.<\/p><p>As a rubyist, you may be familiar with Sandi Metz&rsquo;s &ldquo;<a href=\"https:\/\/thoughtbot.com\/blog\/sandi-metz-rules-for-developers\">rules<\/a>&rdquo;. I think you could make an argument that all four of them deal with concerns about making things too big. Two of the four provide direct heuristics we can apply to determining when something is &ldquo;too big&rdquo;:<\/p><blockquote><ol><li>Classes can be no longer than one hundred lines of code.<\/li><li>Methods can be no longer than five lines of code.<\/li><\/ol><\/blockquote><p>If you agree with those is an entirely different conversation. But there is <em>something<\/em> we could use to ground a discussion on when something is &ldquo;too big&rdquo;.<\/p><h2 id=\"trimming-the-tree\">Trimming the tree <a href=\"#trimming-the-tree\">\ud83d\udd17<\/a><\/h2><p>Rails&rsquo; default routing provides another useful grounding. And that is to stick with the standard seven routes in a controller. More than that and you risk something becoming &ldquo;too big&rdquo;.<\/p><p>That doesn&rsquo;t mean that seven is the correct number of things before it&rsquo;s too big, except that <a href=\"https:\/\/en.wikipedia.org\/wiki\/The_Magical_Number_Seven,_Plus_or_Minus_Two\">maybe it is<\/a>. We might not even <em>get<\/em> to seven, like in our example. We only have TWO methods in that controller. By all of Sandi&rsquo;s rules, we didn&rsquo;t introduce something that was too big. Yet I&rsquo;d still propose reducing it to one by introducing a new controller.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LaurelWreathBranchesController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">create<\/span><\/span><\/span><span style=\"display:flex;\"><span> @laurel_wreath <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">LaurelWreath<\/span><span style=\"color:#f92672\">.<\/span>find(params<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:id<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @laurel_wreath<span style=\"color:#f92672\">.<\/span>branches <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>find(params<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:branch_user_id<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> redirect_to laurel_wreath_path(@laurel_wreath)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>None of the implementation changed here. We don&rsquo;t get a performance benefit out of doing this. What we get is a way to order the <a href=\"https:\/\/youtu.be\/sicUhD1n-es?si=FLKjEc28Yh7fmaJm\">chaos of life<\/a>. We have a mechanism to prevent any one controller from becoming &ldquo;too big&rdquo;. No matter what your definition may be.<\/p><h2 id=\"thoughts-to-leaf-you-with\">Thoughts to leaf you with <a href=\"#thoughts-to-leaf-you-with\">\ud83d\udd17<\/a><\/h2><p>Why break this out into a separate controller? You may gain some benefits in clarity around naming. You can clump related functionality together. You&rsquo;re not tied to controllers named after the ActiveRecord resource they&rsquo;re related to. It&rsquo;s harder to do, because naming things is hard. But by doing the hard work, we spend the time to consider how to best express an idea. We&rsquo;re not limiting ourselves to how to do some functionality and stopping there.<\/p><p>The impact of code written for a feature doesn&rsquo;t end when it&rsquo;s shipped. We need to maintain and understand that code for the remainder of its useful life. Knowing when something has too many responsibilities? Knowing when it&rsquo;s too big? Knowing when it has too much gravitational pull? That&rsquo;s all hard. By leaning on the conventions that Rails provides, we have a tidy rule of thumb to use. In this case, it helps in reducing the complexity of our controllers. Rails provides the ability to extend it. It&rsquo;s not &ldquo;wrong&rdquo; to add another method. But just because we can, <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2019\/\">should we<\/a>?<\/p><p>The next time you find yourself adding &ldquo;just one more custom action&rdquo; to a Rails controller, consider not resting on your laurels. You already came up with a way to make it work. Awesome! Can you use HTTP verbs to your advantage to demonstrate intent and meaning? Ask yourself if you can prevent that controller from growing in size and responsibility.<\/p><p><strong>Then<\/strong> declare victory.<\/p>"},{"title":"Frequently Played November 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-11\/","pubDate":"Tue, 04 Nov 2025 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-11\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"second-best\">Second Best <a href=\"#second-best\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/DzFCmurKYOE?si=IwDCsJbyRrjr1Zqx\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The &ldquo;Live From The Pyre&rdquo; videos they&rsquo;ve been releasing to accompany the tracks on the new album have been treats.<\/p><p><a href=\"https:\/\/genius.com\/37251192\">Full Lyrics<\/a><\/p><blockquote><p>What do I do to be better for you?<br>And your hands and tears are all lost, to the wind<\/p><\/blockquote><h2 id=\"sympathy-magic\">Sympathy Magic <a href=\"#sympathy-magic\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/SbPvXYy8tVM?si=hxPqfqabTsJrIFR1\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Florence&rsquo;s voice is other-worldly. Powerful. Angelic. Haunting.<\/p><p><a href=\"https:\/\/genius.com\/Florence-the-machine-sympathy-magic-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>So I don&rsquo;t have to be worthy<br>I no longer try to be good<br>It didn&rsquo;t keep me safe<br>Like you told me that it would<br>So come on, tear me wide open<br>&lsquo;Til I&rsquo;m losing my mind<br>&lsquo;Til I cannot contain it<\/p><\/blockquote>"},{"title":"Tilt-back Amp Stand","link":"https:\/\/kevinjmurphy.com\/posts\/tilt-back-amp-stand\/","pubDate":"Mon, 13 Oct 2025 15:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/tilt-back-amp-stand\/","description":"<h2 id=\"current-setup\">Current Setup <a href=\"#current-setup\">\ud83d\udd17<\/a><\/h2><p>I have a <a href=\"https:\/\/www.boss.info\/us\/products\/katana-50_mk2\/\">Boss Katana-50 MkII<\/a> combo guitar amp. This is far from unique, but it serves my needs. It more than serves my needs, given I pretty much use it as a pedal platform.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/before.jpg\" alt=\"My amp on the floor, with my pedalboard in front of it\"><\/figure><p>You could argue that this is redundant given the built-in features of the Katana, and you wouldn&rsquo;t be wrong. But it&rsquo;s more fun for me to tweak knobs and dials that I can more easily see than play with presets. Or, even worse, have to connect it to my computer. I&rsquo;m trying to get away from the computer!<\/p><p>Anyway, it works great for my use case, with one minor problem. I&rsquo;m guessing it might sound a little&hellip;more like it&rsquo;s supposed to&hellip;if the speaker wasn&rsquo;t pointed at my feet.<\/p><p>I had an opportunity to reunite with my tools for a bit, and I wanted to use that chance to build a stand for the amp. Something that would allow it to tilt so the speaker is pointing towards my ears.<\/p><h2 id=\"design\">Design <a href=\"#design\">\ud83d\udd17<\/a><\/h2><p>I didn&rsquo;t plan this out in as much detail as I normally do. That was because of another important constraint I had: I didn&rsquo;t want to spend any money. I wanted to use all scraps and hardware I had available. But I also wasn&rsquo;t around my scrap wood when I was designing, so I need to be pretty flexible.<\/p><p>Pretty much, I took a bunch of pictures of my setup and made a bunch of measurements. I particularly focused on what angle I want the amp to be at. I didn&rsquo;t have a protractor, so instead I measured how high of a rise I wanted from the front to back (somewhere between 4&quot;-5&quot;; I told you, I wasn&rsquo;t being detailed), and thought I&rsquo;d figure it out later.<\/p><p>Then I searched around for some YouTube inspiration, such as <a href=\"https:\/\/youtu.be\/nvZX5zLZHZI?si=0qCmeExCvjiQNDQk\">this<\/a> or <a href=\"https:\/\/youtu.be\/A12kdVc-2Lg?si=3Znc7nm-jzpSm_zT\">this<\/a>.<\/p><p>I liked how minimal the first stand is, especially given I wasn&rsquo;t sure how much wood I had to work with. However, I liked the rear support that the second provided. The first stand is for a smaller amp, and I was concerned the Katana 50 would be too top-heavy. Especially for the angle I wanted it to be at.<\/p><p>I became particularly enamored with a <a href=\"https:\/\/youtu.be\/kN4ymuU--Qw?si=0hg49sOd4e9z4m3n\">third option<\/a>. It&rsquo;s really basic, had a rear support, and had a hinge so it could be put away easily. I knew I had a door hinge at the shop I could use for it.<\/p><p>With all that in mind, and amp in the back seat of the car, I headed over to my shop\/tools\/scrap pile.<\/p><h2 id=\"rummaging\">Rummaging <a href=\"#rummaging\">\ud83d\udd17<\/a><\/h2><p>Upon arrival, I went searching for options. I found a run of 3\/4&quot; plywood that was about 7&quot; tall and 24&quot; long. I figured this would work to allow me to have some structure underneath while supporting the angle I wanted. The angle which I figured out was around 35 degrees, now that I had a protractor in hand.<\/p><p>I used the protractor and a combination square to mark out a triangle to cut out of the plywood, made it happen with my jigsaw, and repeated it to have both sides.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/support_sides.jpg\" alt=\"The initial sides of the stand, cut with a jigsaw\"><\/figure><h2 id=\"initial-fit\">Initial Fit <a href=\"#initial-fit\">\ud83d\udd17<\/a><\/h2><p>I put the amp on the two supports. It became immediately evident that the hinge design wouldn&rsquo;t work with what I had cut. There just wasn&rsquo;t enough support. Even with the supports parallel, rather than hinged, the design suffered from my concern with the very first design I looked at. The amp is too top-heavy. It <strong>would<\/strong> rest on the supports, but it easily wanted to tip back.<\/p><h2 id=\"rear-support\">Rear Support <a href=\"#rear-support\">\ud83d\udd17<\/a><\/h2><p>I did figure that I could lodge another piece of wood on top of the supports, which the electrical components box of the open-back cabinet could rest on.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/support_test.jpg\" alt=\"Checking the height for a rear support piece to the amp\"><\/figure><p>I found another piece of plywood that was just a bit too tall for the space between the supports and the top innards. I cut it to length. Then I measured and cut notches for it to slide into the supports on top of. This is why I wanted it to be too tall. The notches are as deep as it is too tall.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/support_notch.jpg\" alt=\"The rear support with notches cut out\"><\/figure><h2 id=\"second-fit\">Second Fit <a href=\"#second-fit\">\ud83d\udd17<\/a><\/h2><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/stand_amp.jpg\" alt=\"The amp on the stand, from the back\"><\/figure><p>This worked much better. The amp had support with its tilt. With the notch design, I could still tear it down for travel if needed, much like the hinge inspiration offered.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/stand_alone.jpg\" alt=\"The pieces of the stand without the amp on top of it\"><\/figure><h2 id=\"cleaning-up\">Cleaning Up <a href=\"#cleaning-up\">\ud83d\udd17<\/a><\/h2><p>With the design in place, I had to move on to less exciting matters. I sanded with 120 grit and 220 grit sandpaper.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/sanding_stand.jpg\" alt=\"Sanding the pieces of the stand\"><\/figure><p>I had one more aesthetic concern to overcome. I don&rsquo;t like seeing the plys in plywood. I know it&rsquo;s a vibe, and some people like it, but it&rsquo;s not for me. Even with a painted piece, I can still see the layers, and it annoys me. I also didn&rsquo;t want to spend a ton of time on this, because I had enough to get through in the rest of the afternoon, so I compromised. I edge-banded only the front faces of each support. The only part that should be visible to me when looking at it.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/stand_banding.jpg\" alt=\"Applying edge banding to the front of the tilt-back stand\"><\/figure><p>This involved getting the iron out to heat up the adhesive on the other side of the edge banding, running it along the edge banding when pressed against the faces, pushing them in place, and cleaning up the edges with a utility knife.<\/p><p>I needed to put some sort of finish on the stand, but again, didn&rsquo;t want to spend any money. I had a can of black spray paint, but didn&rsquo;t think it&rsquo;d be enough. I also had a used can of gray primer. I primed the pieces with gray, and then went with one coat of black over each side.<\/p><h2 id=\"platform-for-the-stand\">Platform for the Stand <a href=\"#platform-for-the-stand\">\ud83d\udd17<\/a><\/h2><p>In between coats, I surveyed what other wood I had at my disposal. I found a 24&quot; long segment of plywood and had an idea. My pedalboard is about 20&quot; wide. I could build a platform that the amp stand sits on top of, and I could tuck my pedalboard just barely underneath this platform if I wanted to tidy things up and gain some floorspace in my room.<\/p><p>I cut the depth I wanted the platform to have on the table saw.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/platform_top.jpg\" alt=\"Cutting the platform to size on the table saw\"><\/figure><p>These edges would be visible as well, so I got the iron back out and edge banded all four sides of this piece.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/platform_banding.jpg\" alt=\"Applying edge banding to the platform\"><\/figure><p>I found a length of 2&quot;x2&quot; (actually 1.5&quot;x1.5&quot;) that I could use for the legs. That would give me 21&quot; of clearance for the pedalboard - and for a 20&quot; pedalboard, that&rsquo;ll just barely do.<\/p><p>I cut those to height on the miter saw.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/cut_legs.jpg\" alt=\"Cutting the platform legs\"><\/figure><p>Then there was more sanding.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/sanding_platform.jpg\" alt=\"Sanding the pieces of the platform\"><\/figure><p>I knew I had to paint the legs, but I also didn&rsquo;t have a lot of time to sit around waiting for drying. I had some 1\/4&quot; plywood scraps and some finishing nails. I attached them as little platforms so I could paint all sides of the legs, other than the side that would attach to the bottom of the platform, all in one go.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/leg_painting.jpg\" alt=\"The legs attached to quarter-inch plywood with finishing nails\"><\/figure><p>I had just enough black spray paint to cover the top and sides of the platform, but not the bottom. And also not the legs. I used some white paint for the legs, giving them two quick coats. With that, I was done for the evening.<\/p><h2 id=\"assembly\">Assembly <a href=\"#assembly\">\ud83d\udd17<\/a><\/h2><p>The next day after bringing things back to their final location and letting things dry, I could work on getting things together. I wanted the legs to be a bit inset from the sides of the platform. I also didn&rsquo;t have a lot of room to play with so the pedalboard would fit below it.<\/p><p>I figured out the maximum spacing by flipping the platform over, putting my pedalboard on top of it, and placing the legs such that the pedalboard would fit between them. I could only inset the legs about 1\/8&quot;, but it wasn&rsquo;t nothing. The legs were placed 1.5&quot; off from the front and back of the platform. I didn&rsquo;t have my combination square, which would have made this easier, but I did what I could with a ruler.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/pedalboard_fit.jpg\" alt=\"Checking platform leg placement with the pedalboard on top of it\"><\/figure><p>I told you I ran out of black spray paint. And I didn&rsquo;t want to spend any money. So the bottom of this platform will just be our secret.<\/p><p>I marked the locations of the legs on the bottom of the platform with pencil. I had some corner bracket braces available I could use to attach the legs to the platform. I only used one per leg. It&rsquo;s not the most secure thing, but this also isn&rsquo;t going to be doing much moving.<\/p><p>I sat down on the floor to put them together, and remembered I had a critical piece of equipment at my disposal: a table. I moved things over to the table so I could work more comfortably.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/leg_assembly.jpg\" alt=\"Assembling the legs of the platform\"><\/figure><p>8 pilot holes and screws later, I was ready for the final step: I put some felt on the bottom of each leg, and also on the bottom of the side supports for the amp.<\/p><h2 id=\"final-fit\">Final Fit <a href=\"#final-fit\">\ud83d\udd17<\/a><\/h2><p>I started by sitting the notched support on top of the sides of the tilt-back stand. The amp went in the stand and I ensured the rear support was&hellip;supporting the back. That felt reasonably secure. Then I took the amp off the tilt-back stand, centered the stand on the platform, and put the amp back on the stand.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/fit_without_amp.jpg\" alt=\"The platform with the stand on top of it with no amp\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/final_fit.jpg\" alt=\"The pedalboard in front of the platform that has the amp in the tilt-back stand on top of it\"><\/figure><\/p><p>My pedals can also tuck under the platform.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/amp_stand\/tucked_in.jpg\" alt=\"The pedalboard tucked under the platform\"><\/figure><p>This also gave me some better cable management. I can move the surge protector I have to power and turn on\/off the whole rig onto the platform, making the switch easier for me to reach. The amp footswitch cable can route under the platform up to the amp, same as the output at the end of my signal chain to the input of the amp. My instrument cable to the guitar can also go around the rear of the platform from my pedalboard and coil nicely in front of everything.<\/p><p>I get the speaker directing sound towards my head, rather than my feet, and a cleaned up playing area. Overall, this was a valuable use of an afternoon. Even better to succeed in not spending any money on materials and without designing detailed plans beforehand. Now, off to play&hellip;<\/p>"},{"title":"Frequently Played October 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-10\/","pubDate":"Sun, 05 Oct 2025 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-10\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"halloween\">Halloween <a href=\"#halloween\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/si8Tpp5En-I?si=KlTvqU5y4Fi45EyJ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>&lsquo;Tis the season.<\/p><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-halloween-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Well I think I saw you for the flash of a moment<br>Your broken heart and the body that holds it<br>I lost your scent in the flash of the party<br>The big bright lights, baby, constantly haunt me<br>I&rsquo;ve never been right, have you ever been lied to?<br>I think I just saw the same scars upon you<br>Is this a disguise? Or a masquerade for me?<\/p><\/blockquote><h2 id=\"quiet\">Quiet <a href=\"#quiet\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/w-0Hk0SJagg?si=XAD8AwBgfLTqFepl\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I&rsquo;ve been listening to a lot of Siamese Dream.<\/p><p><a href=\"https:\/\/genius.com\/12199337\">Full Lyrics<\/a><\/p><blockquote><p>Behind me the grace of falling snow<br>Cover up everything you know<br>Come save me from the awful sound<br>Of nothing<\/p><\/blockquote>"},{"title":"Formatting SQL with anbt-sql-formatter","link":"https:\/\/kevinjmurphy.com\/posts\/format-sql-anbt-sql-formatter\/","pubDate":"Sun, 14 Sep 2025 10:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/format-sql-anbt-sql-formatter\/","description":"<h2 id=\"seeing-sql\">Seeing SQL <a href=\"#seeing-sql\">\ud83d\udd17<\/a><\/h2><p>Have you ever needed to display the SQL query used to generate the data in the report you&rsquo;re also showing? Me neither.<\/p><p>UNTIL I DID.<\/p><p>And when I did, I wanted it to look nice. Readable. Formatted.<\/p><p>Something that didn&rsquo;t look like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#e6db74\">&#34;SELECT * from FOOS join bars on foos.id = bars.foo_id WHERE A = true AND B = &#39;test&#39; OR coalesce(status, &#39;unknown&#39;) &lt;&gt; &#39;unknown&#39;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span> <span style=\"color:#f92672\">*<\/span> from <span style=\"color:#66d9ef\">FOOS<\/span> join bars on foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id <span style=\"color:#66d9ef\">WHERE<\/span> A <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span> <span style=\"color:#66d9ef\">AND<\/span> B <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#39;test&#39;<\/span> <span style=\"color:#66d9ef\">OR<\/span> coalesce(status, <span style=\"color:#e6db74\">&#39;unknown&#39;<\/span>) <span style=\"color:#f92672\">&lt;&gt;<\/span> <span style=\"color:#e6db74\">&#39;unknown&#39;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Reading that, I felt broken. And not in the <a href=\"https:\/\/thebloggess.com\/broken\/\">best possible way<\/a>. I didn&rsquo;t feel any positive vibes. Still don&rsquo;t.<\/p><p>I started thinking about needing to ante up for the <a href=\"https:\/\/blog.ansi.org\/sql-standard-iso-iec-9075-2023-ansi-x3-135\/\">standard documentation<\/a> and do some light reading of the nine parts. And then I&rsquo;d <a href=\"https:\/\/en.wikipedia.org\/wiki\/Parsing\">parse<\/a> the statement, using a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lexical_analysis\">lexer<\/a> to generate tokens to determine how to format this string.<\/p><p>Then I realized what time it was. I forgot to mention this, but it was 4pm on Friday. Not exactly the best time to delve into this intellectual pursuit when I had a weekend of not parsing SQL planned.<\/p><p>So instead I did the next best thing - I searched for the result of someone else&rsquo;s intellectual pursuit of the same problem to see if I could apply it. And that&rsquo;s how I found the <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\">anbt-sql-formatter<\/a> gem.<\/p><h2 id=\"initial-formatting\">Initial Formatting <a href=\"#initial-formatting\">\ud83d\udd17<\/a><\/h2><p>Very generally, anbt-sql-formatter works by setting up the set of rules you want it to use to format the SQL, supplying that to a formatter, and then asking the formatter to apply those rules to the SQL.<\/p><p>You&rsquo;re not on your own to define these rules. With no modifications, it&rsquo;ll apply a default set of rules.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> require <span style=\"color:#e6db74\">&#34;anbt-sql-formatter\/formatter&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> rule <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> formatter <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Formatter<\/span><span style=\"color:#f92672\">.<\/span>new(rule)<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts formatter<span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT * from FOOS WHERE A = true AND B = &#39;test&#39;&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">WHERE<\/span><\/span><\/span><span style=\"display:flex;\"><span> A <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">TRUE<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">AND<\/span> B <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#39;test&#39;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now, that may not be to your taste, but that&rsquo;s ok - as I mentioned, we can tweak the rules. The rest of this post will describe the different options we can use to customize these rules.<\/p><p>As a starting point, we&rsquo;ll build a class to encapsulate this formatting:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;anbt-sql-formatter\/formatter&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SqlFormatter<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">format<\/span>(sql)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">new<\/span><span style=\"color:#f92672\">.<\/span>format(sql)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @formatter <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Formatter<\/span><span style=\"color:#f92672\">.<\/span>new(rule)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">format<\/span>(sql)<\/span><\/span><span style=\"display:flex;\"><span> @formatter<span style=\"color:#f92672\">.<\/span>format(sql)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rule<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">.<\/span>new<span style=\"color:#f92672\">.<\/span>tap <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>rule<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> rules(rule)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The currently empty <code>rules<\/code> method is what we&rsquo;ll modify to demonstrate each option.<\/p><h2 id=\"rule-summary\">Rule Summary <a href=\"#rule-summary\">\ud83d\udd17<\/a><\/h2><p>The rest of this post serves as a reference to describe the different rules you can apply. If you don&rsquo;t want to read this from top to bottom, you may use this summary to guide what sections to dig into.<\/p><ul><li><a href=\"#keyword\">keyword<\/a>: What casing to apply to all keywords.<\/li><li><a href=\"#indent_string\">indent_string<\/a>: The string used to show indentation.<\/li><li><a href=\"#function_names\">function_names<\/a>: The list of known function names.<\/li><li><a href=\"#space_after_comma\">space_after_comma<\/a>: Whether to add a space after a comma.<\/li><li><a href=\"#kw_plus1_indent_x_nl\">kw_plus1_indent_x_nl<\/a>: The list of keywords to add a newline after and indent what follows one level deeper.<\/li><li><a href=\"#kw_minus1_indent_nl_x_plus1_indent\">kw_minus1_indent_nl_x_plus1_indent<\/a>: The list of keywords to add a newline before, one level of indentation shallower, and then indenting what follows one level deeper.<\/li><li><a href=\"#kw_nl_x\">kw_nl_x<\/a>: The list of keywords to add a newline before.<\/li><li><a href=\"#kw_nl_x_plus1_indent\">kw_nl_x_plus1_indent<\/a>: The list of keywords to add a newline before, indented one level deeper.<\/li><li><a href=\"#kw_multi_words\">kw_multi_words<\/a>: The list of multi-word keywords.<\/li><li><a href=\"#in_values_num\">in_values_num<\/a>: The number of entries in an <code>IN<\/code> statement to display on one line.<\/li><\/ul><h2 id=\"keyword\">keyword <a href=\"#keyword\">\ud83d\udd17<\/a><\/h2><p>I tend to use a lot of UPPERCASE when I write SQL. I don&rsquo;t know why. Maybe you do too. And maybe you&rsquo;d wish it displayed lowercase. Or you want to write it in whatever casing you&rsquo;d like, but have the keywords show up in uppercase consistently. That&rsquo;s what this first rule can dictate for us, with the help of some constants.<\/p><h3 id=\"none\">none <a href=\"#none\">\ud83d\udd17<\/a><\/h3><p>With this option, no modifications are made to the casing of keywords.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>keyword <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">KEYWORD_NONE<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT * from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> from<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><p>Here, the <code>SELECT<\/code> keyword is in uppercase characters, as it was in the original string. The <code>from<\/code> keyword is in lowercase, also matching the input string. No changes were made to either keyword.<\/p><h3 id=\"uppercase\">uppercase <a href=\"#uppercase\">\ud83d\udd17<\/a><\/h3><p>We can also force all keywords to be in uppercase.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>keyword <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">KEYWORD_UPPER_CASE<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT * from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><h3 id=\"lowercase\">lowercase <a href=\"#lowercase\">\ud83d\udd17<\/a><\/h3><p>Or all keywords can be transformed to lowercase.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>keyword <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">KEYWORD_LOWER_CASE<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT * from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>select<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> from<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"indent_string\">indent_string <a href=\"#indent_string\">\ud83d\udd17<\/a><\/h2><p>Sure, a certain number of spaces seems like a reasonable <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L60\">default<\/a>. But, with this rule, we can use anything we want.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>indent_string <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;&lt;-&gt;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT * from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&lt;-&gt;&lt;-&gt;*<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&lt;-&gt;<\/span><span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&lt;-&gt;&lt;-&gt;<\/span><span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now, this may seem ridiculous. And that&rsquo;s because this example is. However, it became incredibly useful for how I needed to apply this. I needed to show the SQL query on a website using Rails. I used the <code>simple_format<\/code> <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActionView\/Helpers\/TextHelper.html#method-i-simple_format\">method<\/a> to format the text. However, one thing it didn&rsquo;t do is apply the spaces for indentation.<\/p><p>But, by using a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Sentence_spacing_in_digital_media#Character_encodings\">special character<\/a> for indentation, the <code>simple_format<\/code> helper method now indents my text.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>indent_string <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;&amp;emsp;&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"function_names\">function_names <a href=\"#function_names\">\ud83d\udd17<\/a><\/h2><p>By <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L87-L115\">default<\/a>, the gem recognizes a set of functions as&hellip;functions. And it formats functions by placing the entirety of the function on one line. However, there may be other functions you want formatted as a function, which will put it on its own line.<\/p><p>Consider the default formatting of our use of <code>COALESCE<\/code>, which is a function the gem does not recognize by default.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT COALESCE(status, &#39;unknown&#39;) from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">COALESCE<\/span> (<\/span><\/span><span style=\"display:flex;\"><span> status<\/span><\/span><span style=\"display:flex;\"><span> ,<span style=\"color:#e6db74\">&#39;unknown&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>function_names <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;COALESCE&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT COALESCE(status, &#39;unknown&#39;) from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">COALESCE<\/span>( status ,<span style=\"color:#e6db74\">&#39;unknown&#39;<\/span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"space_after_comma\">space_after_comma <a href=\"#space_after_comma\">\ud83d\udd17<\/a><\/h2><p>The specifies the highly-important standard of whether a comma in the SQL statement should be followed by a space. By <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L62\">default<\/a>, there is no space after the comma.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>function_names <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;COALESCE&#34;<\/span> <span style=\"color:#75715e\"># See function_names section for details<\/span><\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>space_after_comma <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;SELECT coalesce(status, &#39;unknown&#39;) from FOOS&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">COALESCE<\/span>( status , <span style=\"color:#e6db74\">&#39;unknown&#39;<\/span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FOOS<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"kw_plus1_indent_x_nl\">kw_plus1_indent_x_nl <a href=\"#kw_plus1_indent_x_nl\">\ud83d\udd17<\/a><\/h2><p>This is specifying how particular keywords (kw) should be formatted. The &ldquo;x&rdquo; refers to the keyword, &ldquo;nl&rdquo; is for a newline. The name of the setting is meant to demonstrate how this will format the keyword. This is showing that the keyword will be followed by a newline that&rsquo;s indented one level in from the prior line.<\/p><p>There are a set of keywords that have this formatting applied by <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L66\">default<\/a>.<\/p><p>With no change in default formatting, here is how the gem formats the <code>JOIN<\/code> keyword:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_plus1_indent_x_nl <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos <span style=\"color:#66d9ef\">JOIN<\/span><\/span><\/span><span style=\"display:flex;\"><span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><h2 id=\"kw_minus1_indent_nl_x_plus1_indent\">kw_minus1_indent_nl_x_plus1_indent <a href=\"#kw_minus1_indent_nl_x_plus1_indent\">\ud83d\udd17<\/a><\/h2><p>This is specifying how particular keywords (kw) should be formatted. The &ldquo;x&rdquo; refers to the keyword, &ldquo;nl&rdquo; is for a newline. The name of the setting is meant to demonstrate how this will format the keyword. This is showing that the keyword will be put on a newline that&rsquo;s indented one level out from the prior line. The line following the keyword will be indented one level in from the line with the keyword. From what I&rsquo;ve seen, the next word following the keyword is immediately placed on the next line with the indenting, though the setting name does not have a &ldquo;nl&rdquo; after the keyword &ldquo;x&rdquo;.<\/p><p>There are a set of keywords that have this formatting applied by <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L71-L72\">default<\/a>.<\/p><p>With no change in default formatting, here is how the gem formats the <code>JOIN<\/code> keyword:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_minus1_indent_nl_x_plus1_indent <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">JOIN<\/span><\/span><\/span><span style=\"display:flex;\"><span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><h2 id=\"kw_nl_x\">kw_nl_x <a href=\"#kw_nl_x\">\ud83d\udd17<\/a><\/h2><p>This is specifying how particular keywords (kw) should be formatted. The &ldquo;x&rdquo; refers to the keyword, &ldquo;nl&rdquo; is for a newline. The name of the setting is meant to demonstrate how this will format the keyword. This is showing that the keyword will be put on a newline at the same level of indentation as the prior line.<\/p><p>There are a set of keywords that have this formatting applied by <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L80\">default<\/a>.<\/p><p>With no change in default formatting, here is how the gem formats the <code>JOIN<\/code> keyword:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_nl_x <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><h2 id=\"kw_nl_x_plus1_indent\">kw_nl_x_plus1_indent <a href=\"#kw_nl_x_plus1_indent\">\ud83d\udd17<\/a><\/h2><p>This is specifying how particular keywords (kw) should be formatted. The &ldquo;x&rdquo; refers to the keyword, &ldquo;nl&rdquo; is for a newline. The name of the setting is meant to demonstrate how this will format the keyword. This is showing that the keyword will be put on a newline that&rsquo;s indented one level in from the prior line.<\/p><p>There are a set of keywords that have this formatting applied by <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L76\">default<\/a>.<\/p><p>With no change in default formatting, here is how the gem formats the <code>JOIN<\/code> keyword:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_nl_x_plus1_indent <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><h2 id=\"kw_multi_words\">kw_multi_words <a href=\"#kw_multi_words\">\ud83d\udd17<\/a><\/h2><p>Some keywords that you want to apply formatting to are actually more than one word in SQL. This allows you to specify the multi-word combinations that should be treated as a keyword. There is a <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\/blob\/97827ccc8519d19b93e613901bec434eb43c210e\/lib\/anbt-sql-formatter\/rule.rb#L83\">default<\/a> set of multi-word keywords the gem uses.<\/p><p><strong>Setting the rule<\/strong><\/p><p>Here we&rsquo;ll use this to specify <code>INNER JOIN<\/code> as a multi-word keyword and also apply how we want that keyword to be <a href=\"#kw_nl_x\">formatted<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_multi_words <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;INNER JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>kw_nl_x <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;INNER JOIN&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos inner join bars on foos.id = bars.foo_id&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">INNER<\/span> <span style=\"color:#66d9ef\">JOIN<\/span> bars<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ON<\/span> foos<span style=\"color:#f92672\">.<\/span>id <span style=\"color:#f92672\">=<\/span> bars<span style=\"color:#f92672\">.<\/span>foo_id<\/span><\/span><\/code><\/pre><\/div><h2 id=\"in_values_num\">in_values_num <a href=\"#in_values_num\">\ud83d\udd17<\/a><\/h2><p>This is used to specify how many items inside of an <code>IN<\/code> statement will appear on a single line.<\/p><h3 id=\"default\">Default <a href=\"#default\">\ud83d\udd17<\/a><\/h3><p>With no modifications, each element inside the <code>IN<\/code> statement will appear on its own line.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos where baz in (&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;)&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">WHERE<\/span><\/span><\/span><span style=\"display:flex;\"><span> baz <span style=\"color:#66d9ef\">IN<\/span> (<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#39;a&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> ,<span style=\"color:#e6db74\">&#39;b&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> ,<span style=\"color:#e6db74\">&#39;c&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> ,<span style=\"color:#e6db74\">&#39;d&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><\/code><\/pre><\/div><h3 id=\"one-line\">One Line <a href=\"#one-line\">\ud83d\udd17<\/a><\/h3><p>You can force all the elements to appear on a single line using a special constant.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>in_values_num <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">AnbtSql<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Rule<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ONELINE_IN_VALUES_NUM<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos where baz in (&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;)&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">WHERE<\/span><\/span><\/span><span style=\"display:flex;\"><span> baz <span style=\"color:#66d9ef\">IN<\/span> (<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#39;a&#39;<\/span> ,<span style=\"color:#e6db74\">&#39;b&#39;<\/span> ,<span style=\"color:#e6db74\">&#39;c&#39;<\/span> ,<span style=\"color:#e6db74\">&#39;d&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><\/code><\/pre><\/div><h3 id=\"specifying-the-number\">Specifying the number <a href=\"#specifying-the-number\">\ud83d\udd17<\/a><\/h3><p>You can also define an explicit number of elements to appear on a single line.<\/p><p><strong>Setting the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">rules<\/span>(rule)<\/span><\/span><span style=\"display:flex;\"><span> rule<span style=\"color:#f92672\">.<\/span>in_values_num <span style=\"color:#f92672\">=<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Using the rule<\/strong><\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> puts <span style=\"color:#66d9ef\">SqlFormatter<\/span><span style=\"color:#f92672\">.<\/span>format(<span style=\"color:#e6db74\">&#34;select * from foos where baz in (&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;)&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">SELECT<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">*<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">FROM<\/span><\/span><\/span><span style=\"display:flex;\"><span> foos<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">WHERE<\/span><\/span><\/span><span style=\"display:flex;\"><span> baz <span style=\"color:#66d9ef\">IN<\/span> (<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#39;a&#39;<\/span> ,<span style=\"color:#e6db74\">&#39;b&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> ,<span style=\"color:#e6db74\">&#39;c&#39;<\/span> ,<span style=\"color:#e6db74\">&#39;d&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><\/code><\/pre><\/div><h2 id=\"conclusion\">Conclusion <a href=\"#conclusion\">\ud83d\udd17<\/a><\/h2><p>The <a href=\"https:\/\/github.com\/sonota88\/anbt-sql-formatter\">anbt-sql-formatter<\/a> gem is a great starting point for formatting SQL statements and includes many customizations to modify the formatting to your liking.<\/p>"},{"title":"New Wall Art","link":"https:\/\/kevinjmurphy.com\/posts\/new-wall-art\/","pubDate":"Wed, 10 Sep 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/new-wall-art\/","description":"<h2 id=\"glitch-on-plaster\">Glitch on Plaster <a href=\"#glitch-on-plaster\">\ud83d\udd17<\/a><\/h2><p>I recently received some new prints that immediately went into frames and made their way on to my wall. They&rsquo;re all from <a href=\"https:\/\/rob-sheridan.com\/\">Rob Sheridan<\/a>. I first learned about Rob from his time as the Creative Director of Nine InchNails, and I&rsquo;ve enjoyed following his work since then.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_art_wall.jpg\" alt=\"Three prints of Rob Sheridan&#39;s work framed on the wall: &#39;Precipice&#39;, &#39;Reach&#39;, and &#39;Turbulence&#39; from left to right\"><\/figure><p>These are all poorly-taken phone camera pictures of high-quality prints from within frames. Trust that the quality of images you see in front of you do not do justice to their true representation.<\/p><h2 id=\"precipice-second-edition\">Precipice (second edition) <a href=\"#precipice-second-edition\">\ud83d\udd17<\/a><\/h2><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_precipice.jpg\" alt=\"A print of Rob Sheridan&#39;s &#39;Precipice&#39; in a frame\"><\/figure><p>Rob provides some context about this piece:<\/p><blockquote><p>The allure and fear of the unknown; the double-edged sword of feeling like anything is possible, for better or worse. The warm glow of possibility corrupted by an ominous existential dread that the ground beneath us is eroding, and the light on the horizon is cracking apart; that we stand on a fragile precipice where hope and fear are intertwined; the more love you have, the more you have to lose.<\/p><\/blockquote><p>As you might be able to tell, I&rsquo;m drawn to Rob&rsquo;s centering of an individual in disparate environments around them. I see the glitch around the figure here as both a prison and a protective layer between them and the outside world. Which it&rsquo;s acting as at the moment depends on your perspective and frame of mind.<\/p><p>I have print 42\/55; you should <a href=\"https:\/\/robsheridan.storenvy.com\/collections\/1829473-latest-additions\/products\/36906567-precipice-small-giclee-print-8-x-10-second-edition\">get yours<\/a>.<\/p><h2 id=\"reach-remastered-vertical-edition\">Reach (remastered vertical edition) <a href=\"#reach-remastered-vertical-edition\">\ud83d\udd17<\/a><\/h2><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_reach.jpg\" alt=\"A print of Rob Sheridan&#39;s &#39;Reach&#39; in a frame\"><\/figure><p>I spend the majority of my daily life attempting to provide instructions to a computer. Navigating a literal digital sea. We spend untold amounts of time wading in artifices meant to evoke experiences similar to, but not quite matching, the physical world.<\/p><p>It can take so much energy and effort to pull yourself up from those depths. To seek connection. You can succeed in extending yourself to meet the world only to find&hellip;no one is there.<\/p><p>I have print 11\/25; you should <a href=\"https:\/\/robsheridan.storenvy.com\/collections\/1829473-latest-additions\/products\/36716500-reach-small-giclee-print-8-x-10-remastered-vertical-edition\">get yours<\/a>.<\/p><h2 id=\"turbulence\">Turbulence <a href=\"#turbulence\">\ud83d\udd17<\/a><\/h2><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_turbulence.jpg\" alt=\"A print of Rob Sheridan&#39;s &#39;Turbulence&#39; in a frame\"><\/figure>Rob succinctly introduces this as:<\/p><blockquote><p>A new glitch piece about surviving, rising above, holding on to yourself when it feels like you\u2019re coming apart.<\/p><\/blockquote><p>This is the image that evokes the most visceral reaction in me. Unfortunately, I find myself struggling to explain it accurately. Something about the degradations of the repeating figure really gets me. The color palette feels both ominous while still warm and inviting. Like I belong there, but I don&rsquo;t know if I want to be there.<\/p><p>I have print 2\/25; you should <a href=\"https:\/\/robsheridan.storenvy.com\/collections\/1829473-latest-additions\/products\/36951245-turbulence-small-giclee-print-8-x-10\">get yours<\/a>.<\/p><h2 id=\"bonus\">Bonus <a href=\"#bonus\">\ud83d\udd17<\/a><\/h2><p>Rob and Steph were kind enough to include a sticker with a personalized note. Whether or not any of these pieces speak to you, I suggest checking out the rest of Rob&rsquo;s <a href=\"https:\/\/robsheridan.storenvy.com\/\">store<\/a> and <a href=\"https:\/\/rob-sheridan.com\/\">work<\/a>.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_sticker_front.jpg\" alt=\"A sticker of Turbulence with a link to rob-sheridan.com\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_09_10_sticker_back.jpg\" alt=\"The back of the sticker, where Rob wrote, &#39;Thanks Kevin! -Rob&#39;\"><\/figure><\/p><p>Thank <em>you<\/em>, Rob.<\/p><h2 id=\"nothing-studios-2000\">Nothing Studios 2000 <a href=\"#nothing-studios-2000\">\ud83d\udd17<\/a><\/h2><p>With the 25th anniversary of <a href=\"https:\/\/www.patreon.com\/posts\/144077283\">Things Falling Apart<\/a>, Rob pulled some rare prints out, and I got a matching set. These photos are important to me because of their setting (Nothing Studios) and time (2000) they were taken.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_12_21_texture_set.jpg\" alt=\"Both texture prints in frames mounted on the wall. In the reflection you can see artwork from the opposite walls.\"><\/figure><h2 id=\"texture-studio-i\">texture: studio I <a href=\"#texture-studio-i\">\ud83d\udd17<\/a><\/h2><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_12_21_texture_studio_i.jpg\" alt=\"A print of Rob Sheridan&#39;s texture: studio i. A close-up of synthesizer keys taken with a macro lens. Signed by Rob and labeled 62\/65.\"><\/figure><blockquote><p>Personal photography taken on 35mm film of the textures of Nothing Studios, New Orleans, where I was living in the year 2000, at the beginning of my journey with macro lenses. Macro photography opened up new portals of expression all around me; emotions from the inanimate, stories from mundane details. It inspired my artwork for Nine Inch Nails&rsquo; &ldquo;Things Falling Apart&rdquo; that same year, and the process of exploring details of instruments and the texture of the studio environment would lay the groundwork for my photography on Nine Inch Nails&rsquo; &ldquo;Ghosts I-IV&rdquo; in 2008.<\/p><\/blockquote><p>Unfortunately, this got bent in transit. I&rsquo;ll chalk it up to a real-world glitch.<\/p><p>I have print 62\/65.<\/p><h2 id=\"texture-studio-ii\">texture: studio II <a href=\"#texture-studio-ii\">\ud83d\udd17<\/a><\/h2><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_12_21_texture_studio_ii.jpg\" alt=\"A print of Rob Sheridan&#39;s texture: studio ii. A close-up of an acoustic guitar near the soundhole taken with a macro lens. Signed by Rob and labeled 62\/65.\"><\/figure><blockquote><p>Personal photography taken on 35mm film of the textures of Nothing Studios, New Orleans, where I was living in the year 2000, at the beginning of my journey with macro lenses. Macro photography opened up new portals of expression all around me; emotions from the inanimate, stories from mundane details. It inspired my artwork for Nine Inch Nails&rsquo; &ldquo;Things Falling Apart&rdquo; that same year, and the process of exploring details of instruments and the texture of the studio environment would lay the groundwork for my photography on Nine Inch Nails&rsquo; &ldquo;Ghosts I-IV&rdquo; in 2008.<\/p><\/blockquote><p>I have print 62\/65.<\/p><h2 id=\"bonus-2\">Bonus 2 <a href=\"#bonus-2\">\ud83d\udd17<\/a><\/h2><p>Another order; another sticker.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_12_21_glitch_skull_astronaut_sticker.jpg\" alt=\"A sticker of Glitch Skull Astronaut with Rob Sheridan&#39;s name\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/2025_12_21_glitch_skull_astronaut_sticker_back.jpg\" alt=\"The back of the sticker, where Rob wrote, &#39;Thanks Kevin! -Rob&#39;\"><\/figure><\/p>"},{"title":"Frequently Played Sept 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-09\/","pubDate":"Sat, 06 Sep 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-09\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"the-blues-mary\">The Blues, Mary <a href=\"#the-blues-mary\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/d5hJV3dM5OA?si=ZVDhPccPCyvMoFxf\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This song is a precursor, a warning, a premonition.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-the-blues-mary-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I learned how to cry<br>In them lonesome September nights<br>I learned to get by<br>With the dogs and the dirt and the charm of the street<br>I fell asleep most nights<br>With your pictures right behind my eyes, your eyes<\/p><\/blockquote><h2 id=\"and-all-that-could-have-been\">And All That Could Have Been <a href=\"#and-all-that-could-have-been\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2U0flA_Yp64?si=k2V_SmRzUMzxsTf3\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I started this series one year ago. I think it&rsquo;d be a funny bit to just repeatthe same songs year after year. But this isn&rsquo;t a bit. I&rsquo;m just in the same spot.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-and-all-that-could-have-been-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Please<br>Take this<br>And run far away<br>Far as you can see<br>I am tainted<br>And happiness<br>And peace of mind<br>Were never meant for me<br>All these pieces<br>And promises<br>And left-behinds<br>If only I could see<br>In my nothing<br>You meant everything<br>Everything to me<\/p><\/blockquote>"},{"title":"Frequently Played August 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-08\/","pubDate":"Sun, 10 Aug 2025 09:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-08\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"this-is-the-killer-speaking\">This Is The Killer Speaking <a href=\"#this-is-the-killer-speaking\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/jDmQLeyUWZ8?si=kxC6eC4aaFiWpllp&amp;start=2786\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I pre-ordered the new The Last Dinner Party album right away. Yes, I buy music.I&rsquo;m old. I&rsquo;ll be receiving a &ldquo;compact disc&rdquo; when it&rsquo;s released.<\/p><p><a href=\"https:\/\/genius.com\/The-last-dinner-party-this-is-the-killer-speaking-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>If only you\u2019d been honest, could have spared this bloodshed<br>Now I\u2019m wanted \u2018cross several county lines<br>When your hand is bigger than my heart<br>You can crush it, just the way I like<br>You got a whole lotta\u2019 nerve and a whole lotta\u2019 spite<br>When you leave, don\u2019t look me in the eye<\/p><\/blockquote><h2 id=\"as-alive-as-you-need-me-to-be\">As Alive As You Need Me To Be <a href=\"#as-alive-as-you-need-me-to-be\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/-Sj-FmI5JfA?si=q6EQRMqV13TNH5jI\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I&rsquo;m very much looking forward to stepping out of the world for a few hours andseeing them live again at the end of the month. And back with my favoritedrummer, <a href=\"https:\/\/www.msn.com\/en-us\/music\/news\/josh-freese-on-returning-to-nine-inch-nails-after-foo-fighters-departure-to-be-part-of-that-energy-again-feels-amazing\/ar-AA1JFj2X\">Josh Freese<\/a>.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-as-alive-as-you-need-me-to-be-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Always going to go this way, there&rsquo;s no turning back<br>Never any other way, there&rsquo;s no turning back<br>Everything for a reason, everything for a reason<br>Everything for a reason, everything for a reason<\/p><\/blockquote>"},{"title":"RailsConf 2025 Video","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2025-video\/","pubDate":"Thu, 24 Jul 2025 21:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2025-video\/","description":"<p>The videos for RailsConf 2025 have been posted to Ruby Central&rsquo;s YouTubechannel, and that includes <a href=\"https:\/\/youtu.be\/2WkBHoGAlpY?si=c_SCAvyEckyTH4RS\">my session<\/a>.If you weren&rsquo;t able to join us, I hope you&rsquo;ll check it out.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2WkBHoGAlpY?si=3jp7FNtUbGyEey2Z\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>"},{"title":"RailsConf 2025 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2025-recap\/","pubDate":"Sat, 12 Jul 2025 16:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2025-recap\/","description":"<h2 id=\"railsconf-2025\">RailsConf 2025 <a href=\"#railsconf-2025\">\ud83d\udd17<\/a><\/h2><p>RailsConf recently wrapped up in Philadelphia, Pennsylvania. The 2025 conference, and also the RailsConf event itself is now over as far as we know it. This post is meant to highlight the great work from all involved. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you once they are available. Unfortunately, I couldn&rsquo;t be everywhere, so this covers what I saw.<\/p><h2 id=\"preparing\">Preparing <a href=\"#preparing\">\ud83d\udd17<\/a><\/h2><p>Just as I&rsquo;ve done in the past, I joined Ruby Central members for the CFP coaching sessions. We helped others with their proposals in small groups. This was a great opportunity to meet with prospective speakers and workshop ideas to submit for the conference. I&rsquo;m glad that Ruby Central runs these events. They&rsquo;re a great opportunity for prospective speakers. If you&rsquo;re considering submitting to a future RubyConf, I hope you&rsquo;ll join a session.<\/p><p>I received the honor of being <a href=\"https:\/\/kevinjmurphy.com\/posts\/speaking-at-railsconf-2025\/\">selected as a speaker<\/a> for the final RailsConf. I followed my <a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">preparation playbook<\/a> as usual. I took one deviation. I usually consider some smaller public audience to test it on first. This is both for feedback and to gain comfort running it for a crowd. This talk is very specific to RailsConf and me. Instead I kept it between myself and a very tight group of collaborators.<\/p><p>In a fitting tribute, I booked a seat on the train to travel to RailsConf.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><h3 id=\"365-days-later-moving-from-java-to-ror-and-how-it-changed-everything\">365 Days Later: Moving from Java to RoR and how it changed everything <a href=\"#365-days-later-moving-from-java-to-ror-and-how-it-changed-everything\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#john-dewsnap\">John Dewsnap<\/a> filled us in on the story that started last year at RailsConf 2024. Once FlexCar deployed their Rails monolith, what happened next?<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_john.jpg\" alt=\"John at RailsConf 2025\"><\/figure><h3 id=\"derailing-our-application-how-and-why-we-are-decoupling-our-code-from-rails\">Derailing Our Application: How and Why We Are Decoupling Our Code from Rails <a href=\"#derailing-our-application-how-and-why-we-are-decoupling-our-code-from-rails\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#fito-von-zastrow\">Fito Von Zastrow<\/a> and <a href=\"https:\/\/railsconf.org\/speakers\/#alan-ridlehoover\">Alan Ridlehoover<\/a> spoke about scaling your monolith&rsquo;s code without being tied to Rails&rsquo; framework fundamentals.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_fito_alan.jpg\" alt=\"Fito and Alan at RailsConf 2025\"><\/figure><h3 id=\"yes-you-can-work-on-rails--any-other-gem\">Yes, You Can Work on Rails &amp; any other Gem <a href=\"#yes-you-can-work-on-rails--any-other-gem\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#kasper-timm-hansen\">Kasper Timm Hansen<\/a> shared insights on how to approach new code with openness and curiosity while leveraging what you already know.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_kasper.jpg\" alt=\"Kasper at RailsConf 2025\"><\/figure><h3 id=\"evolution-of-rails-within-rubygemsorg\">Evolution of Rails within RubyGems.org <a href=\"#evolution-of-rails-within-rubygemsorg\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#samuel-giddins\">Samuel Giddins<\/a> and <a href=\"https:\/\/railsconf.org\/speakers\/#nick-quaranto\">Nick Quaranto<\/a> shared the history of RubyGems along with details of where we are today.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_samuel_nick.jpg\" alt=\"Samuel and Nick at RailsConf 2025\"><\/figure><h3 id=\"ruby-internals-a-guide-for-rails-developers\">Ruby Internals: A Guide For Rails Developers <a href=\"#ruby-internals-a-guide-for-rails-developers\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#matheus-richard\">Matheus Richard<\/a> presented a fantastic representation of how various low-level Ruby components that we may take for granted work and come together to serve our Rails applications.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_matheus.jpg\" alt=\"Matheus at RailsConf 2025\"><\/figure><h3 id=\"the-rails-features-we-loved-lost-and-laughed-at\">The Rails Features We Loved, Lost, and Laughed At <a href=\"#the-rails-features-we-loved-lost-and-laughed-at\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#robby-russell\">Robby Russell<\/a> took us on a trip to explore parts of Rails that are gone. With his help, we ensured they aren&rsquo;t forgotten.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_robby.jpg\" alt=\"Robby at RailsConf 2025\"><\/figure><h3 id=\"closing\">Closing <a href=\"#closing\">\ud83d\udd17<\/a><\/h3><p>It&rsquo;s July in Philadelphia. I had no interest in being near a fireside. Instead I joined friends for the rest of the evening.<\/p><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><h3 id=\"rails-then-now-and-next-a-conversation-with-our-community\">Rails Then, Now, and Next: A Conversation with our Community <a href=\"#rails-then-now-and-next-a-conversation-with-our-community\">\ud83d\udd17<\/a><\/h3><p>Aaron Patterson, Sarah Mei, Evan Phoenix, Nadia Odunayo, Chad Fowler, Eileen M. Uchitelle and Marty Haught participated in a panel discussion on Rails, but mostly on the environment that <em>surrounds<\/em> Rails.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_community_panel.jpg\" alt=\"The community panel at RailsConf 2025\"><\/figure><h3 id=\"what-can-we-do-together-a-facilitated-community-building-workshop\">What can we do together? A facilitated community-building workshop <a href=\"#what-can-we-do-together-a-facilitated-community-building-workshop\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#stephanie-minn\">Stephanie Minn<\/a> brought together people from disparate backgrounds to share their experiences, find common ground, and make connections.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_stephanie.jpg\" alt=\"Stephanie at RailsConf 2025\"><\/figure><h3 id=\"ruby-podcast-panel\">Ruby Podcast Panel <a href=\"#ruby-podcast-panel\">\ud83d\udd17<\/a><\/h3><p>David Hill, Drew Bragg, Stephanie Minn, and Chris Oliver once again welcome competition and encourage you to start your own podcast, if you&rsquo;re so inclined.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_podcast_panel.jpg\" alt=\"Podcast Panel at RailsConf 2025\"><\/figure><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><p>I was happy to have so many colleagues join me this year at RailsConf. We found ourselves all sitting together before the opening keynote.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_pubmark.jpg\" alt=\"Pubmark (Tessa, Jeremy, Abigail, Brandon, Shae, and me at RailsConf 2025\"><\/figure><h3 id=\"the-keynote-of-keynotes\">The Keynote of Keynotes <a href=\"#the-keynote-of-keynotes\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#aji-slater\">Aji Slater<\/a> provided a touching retrospective that tightly integrated the messages of <strong>all<\/strong> keynotes available to share from past RailsConfs. By doing so, Aji provided fresh competition for anyone&rsquo;s favorite keynote: theirs.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_aji.jpg\" alt=\"Aji at RailsConf 2025\"><\/figure><h3 id=\"how-10-years-of-railsconfs-can-inform-the-next-10-years-of-your-career\">How 10 years of RailsConfs can inform the next 10 years of your career <a href=\"#how-10-years-of-railsconfs-can-inform-the-next-10-years-of-your-career\">\ud83d\udd17<\/a><\/h3><p>I didn&rsquo;t have much of a choice about which session to attend in this block. Thanks to everyone who joined me.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_kevin.jpg\" alt=\"Kevin at RailsConf 2025\"><\/figure>\ud83d\udcf8 Credit: Stephanie Minn<\/p><h3 id=\"rails-framework-defaults-defusing-the-time-bomb-in-your-upgraded-app\">Rails Framework Defaults: Defusing the Time Bomb in Your Upgraded App <a href=\"#rails-framework-defaults-defusing-the-time-bomb-in-your-upgraded-app\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#josh-puetz\">Josh Puetz<\/a> shared what framework defaults are, how to leverage them, and how to safely introduce them as part of your upgrade process.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_josh.jpg\" alt=\"Josh at RailsConf 2025\"><\/figure><h3 id=\"the-history-of-rails-in-10-blog-posts\">The History of Rails in 10 Blog Posts <a href=\"#the-history-of-rails-in-10-blog-posts\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#joel-quenneville\">Jo\u00ebl Quenneville<\/a> shared the blog posts that pushed the conversation forward in our community, highlighting the impact and importance of the words.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_joel.jpg\" alt=\"Jo\u00ebl at RailsConf 2025\"><\/figure><h3 id=\"the-modern-view-layer-rails-deserves-a-vision-for-2025-and-beyond\">The Modern View Layer Rails Deserves: A Vision for 2025 and Beyond <a href=\"#the-modern-view-layer-rails-deserves-a-vision-for-2025-and-beyond\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#marco-roth\">Marco Roth<\/a> dazzled everyone yet again from the stage with all the work he does when he&rsquo;s not on stage to improve our tooling and developer experience.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_marco.jpg\" alt=\"Marco at RailsConf 2025\"><\/figure><h3 id=\"keynote\">Keynote <a href=\"#keynote\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf.org\/speakers\/#aaron-patterson\">Aaron Patterson<\/a> gave an Aaron Patterson keynote. You could say he gave <em>many<\/em> Aaron Patterson keynotes. By reviewing his past presentations, he shared the reason <strong>why<\/strong> he went to such effort to deliver silly ideas to demonstrate profound concepts: because it was fun.<\/p><p>In doing so, Aaron demonstrated another profound concept. Creativity is what will push the industry forward. Allowing computers to continue to engage in the mundane will keep us where we are. But we&rsquo;ll need humans, and their creativity, to push boundaries. So code for fun. Code for curiosity. Code just because.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_aaron.jpg\" alt=\"Aaron at RailsConf 2025\"><\/figure><h3 id=\"closing-1\">Closing <a href=\"#closing-1\">\ud83d\udd17<\/a><\/h3><p>RailsConf has been incredibly important to me. I won&rsquo;t go into details, because it&rsquo;d take me about <a href=\"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf\/\">30 minutes<\/a>. Instead, I&rsquo;ll highlight how much it meant hearing about what RailsConf meant for <em>others<\/em>. How comforting it was to see everyone back again. How exciting it was to see people experience it for the first time. And, being honest, a bit of sadness thinking of everyone who wasn&rsquo;t there for one reason or another.<\/p><p>When I left the lobby for the evening, I sought out <a href=\"https:\/\/ufuk.dev\/\">Ufuk<\/a> to thank him again and say good-bye. It was the perfect closing interaction.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>Me: You did it! <\/span><\/span><span style=\"display:flex;\"><span>Ufuk: We did it. <\/span><\/span><\/code><\/pre><\/div><p>As usual, Ufuk is right. <strong>We<\/strong> did it. We all did. Thank you. Every one of you.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_real_railsconf.png\" alt=\"Maybe the real RailsConf was the friends we made along the way\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_ruby_friends.png\" alt=\"Ruby Friends compiled from years of conference attendance\"><\/figure><\/p>"},{"title":"How 10 years of RailsConfs can inform the next 10 years of your career","link":"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf\/","pubDate":"Fri, 04 Jul 2025 21:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>RailsConf has played an important role in my professional and personal life. I&rsquo;ve learned about technology in ways I wouldn&rsquo;t have otherwise at RailsConf. I&rsquo;ve learned about myself in ways I wouldn&rsquo;t have otherwise thanks to RailsConf. I&rsquo;ve met people that changed the course of my career thanks to RailsConf. I&rsquo;ve met others who have become dear friends thanks to RailsConf. I&rsquo;ve done things I&rsquo;ve never done before thanks to RailsConf.<\/p><p>The same may be true for you. We should celebrate and reflect on that. Where do we go from here? Let&rsquo;s talk about it together.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2WkBHoGAlpY?si=3jp7FNtUbGyEey2Z\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/youtu.be\/2WkBHoGAlpY?si=c_SCAvyEckyTH4RS\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/how-10-years-of-railsconfs-can-inform-the-next-10-years-of-your-career\">Slides<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf-proposal\/\">Proposal<\/a><\/li><\/ul>"},{"title":"Frequently Played July 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-07\/","pubDate":"Wed, 02 Jul 2025 14:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-07\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"nothing-matters\">Nothing Matters <a href=\"#nothing-matters\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/yFo5BiroJYI?si=5w39AOF6xE0vyarm\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I am definitely consuming all things The Last Dinner Party lately.<\/p><p><a href=\"https:\/\/genius.com\/The-last-dinner-party-nothing-matters-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Even when the cold comes crashing through, I&rsquo;m putting all my bets on you<br>I hope they never understand us<br>I put my heart inside your palms, my home in your arms, now we know<br>Nothing matters, nothing matters<\/p><\/blockquote><h2 id=\"good-time\">Good Time <a href=\"#good-time\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/ldaJyOGDBag?si=XCuvMbWgfVaEhWHs\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I&rsquo;m missing them on tour with Gaslight Anthem this cycle.<\/p><p><a href=\"https:\/\/genius.com\/Counting-crows-good-time-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>So he rushes in to tell you what he did today<br>But he can\u2019t think of what to say<br>I think you listen anyway<\/p><p>He wants to have a good time just like everybody<br>He doesn\u2019t want to fall apart<\/p><p>You watch him as he stutters over what to say<br>It&rsquo;s just a little game you play<br>It&rsquo;s no easier for you some days<br>You wish that you could tell him it\u2019ll be okay<br>But you feel a little shy these days<br>&lsquo;Cause everybody goes away<\/p><\/blockquote><h2 id=\"were-getting-a-divorce-you-keep-the-diner\">We\u2019re Getting a Divorce, You Keep the Diner <a href=\"#were-getting-a-divorce-you-keep-the-diner\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/P6WscA8oaKc?si=Bl8VW00UYfiR4fFk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>From the album Sink or Swim. My life has been about treading water. Keeping my head just above the surface. Trying to take a deep breath when I go under; seeing what it&rsquo;s like below, then pushing my way back for air.<\/p><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-were-getting-a-divorce-you-keep-the-diner-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>On a Sunday morning the whole crowd assembled<br>I&rsquo;ve done some things that I&rsquo;m not too proud of<br>I&rsquo;ve never left you, a deaf ear for longing<br>Some hearts are gallows, I&rsquo;m not here for hangin&rsquo; around<\/p><\/blockquote><h2 id=\"safeword\">safeword <a href=\"#safeword\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/UZQQwU8QA68?si=1xRSN73_eGJvwiP9\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The lyrical content is not a driving factor here, but we do have another datapoint to prove that Halsey rips.<\/p>"},{"title":"How 10 years of RailsConfs can inform the next 10 years of your career - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf-proposal\/","pubDate":"Thu, 26 Jun 2025 20:26:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ten-years-of-railsconf-proposal\/","description":"<h1 id=\"description\">Description <a href=\"#description\">\ud83d\udd17<\/a><\/h1><p>RailsConf has played an important role in my professional and personal life. I&rsquo;ve learned about technology in ways I wouldn&rsquo;t have otherwise at RailsConf. I&rsquo;ve learned about myself in ways I wouldn&rsquo;t have otherwise thanks to RailsConf. I&rsquo;ve met people that changed the course of my career thanks to RailsConf. I&rsquo;ve met others who have become dear friends thanks to RailsConf. I&rsquo;ve done things I&rsquo;ve never done before thanks to RailsConf.<\/p><p>The same may be true for you. We should celebrate and reflect on that. Where do we go from here? Let&rsquo;s talk about it together.<\/p><h1 id=\"session-format\">Session Format <a href=\"#session-format\">\ud83d\udd17<\/a><\/h1><p>Talk<\/p><h1 id=\"level\">Level <a href=\"#level\">\ud83d\udd17<\/a><\/h1><p>Introductory &amp; Overview<\/p><h1 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h1><p>This talk shares my experiences with RailsConf. It highlights the importance of the event, to myself and the community. Thanks to RailsConf, I became a speaker and a Program Committee member. Thanks for RailsConf I\u2019ve heard about, and joined, new companies. Thanks to RailsConf I found community. Thanks to RailsConf I\u2019ve made friends that have a monumental impact on my life. The stories share and reinforce actions the audience can apply. These lessons apply both in this RailsConf and beyond.<\/p><p>The talk will leave the audience feeling like a part of a meaningful event. It ends with a call to action for us all. We&rsquo;re collectively responsible for continuing those benefits without RailsConf.<\/p><p>I intend to structure this talk in the same paradigm as Rails: we\u2019ll model our initial actions, view the results, and control the future. I\u2019ll have code samples related to the different sections to demonstrate my points with code.<\/p><h2 id=\"joining-the-community-model\">Joining The Community (Model) <a href=\"#joining-the-community-model\">\ud83d\udd17<\/a><\/h2><p>In 2014, I was the only developer in a larger company maintaining a Ruby on Rails application. My manager bet me that I could ship a new app more quickly using Rails than with our standard tools. My initial reaction was, &ldquo;what&rsquo;s Rails?&rdquo; He gave me the resources, and cover, to explore and deliver that application. And he was probably right about that bet. Thanks, Ari.<\/p><p>However, I wanted to learn from other people. I wanted to hear how they were using Ruby in their work. I wanted to know if there were different ways I should be writing or thinking about my code. And I didn&rsquo;t have anyone at work I could talk about that with. So, I found a conference: RailsConf. And I convinced my manager (and their managers) to send me.<\/p><p>Attending that conference changed my relationship with Ruby. It changed my connection with my work. I had never seen so many people together all interested in something like a programming framework. Instantly, I wasn&rsquo;t alone, even though I showed up by myself not knowing a single other person.<\/p><p>Remember that seminal talk Sandi Metz gave, doing the <a href=\"https:\/\/youtu.be\/8bZh5LMaSmE?si=U89T-W_J_5EVxEg6\">Gilded Rose kata<\/a>? I&rsquo;m glad you do - I don&rsquo;t. I wasn&rsquo;t there. I was in another talk at the same time. I had no idea who Sandi was. I learned pretty quickly. Like immediately after in the hallway from everyone spilling out of her session. But it was a little late at that point. For the record, I remember the talk I was in instead. They did a good job. But I only became familiar with these people, and their work, from what I picked up at the conference.<\/p><p><strong>Audience Takeaway<\/strong>: Advocate for yourself. At the time, my company was willing to reimburse my costs to attend the conference. It was something they called &ldquo;professional development&rdquo;. But only after I did the work to identify the opportunity and build a business case for it.<\/p><p>As I mentioned, I was there alone. Didn&rsquo;t know a single person. Also, I&rsquo;m terrible at meeting people. But I did know one thing - the Bruins were playing a playoff hockey game while I was there. Maybe one other person liked hockey, and we could watch it together at a bar. Now, I wasn&rsquo;t going to just start asking people. That would take courage. I did the next best thing. I sheepishly mentioned it on social media. At the time, there was this site called Twitter. You could share small (140 character) messages publicly. And if you added a hashtag, maybe other people searching for that would see it. #RailsConf<\/p><p>I did find one other interested person. But our schedules got crossed, and we didn&rsquo;t even catch the game together. We did agree to meet for breakfast the next morning.<\/p><p>Earlier that day, I casually read the giant jobs board. Not because I was looking for a new job. Because I was curious what other companies were using this thing that I was doing on my own. I found a company there that said they were in Boston - close to me.<\/p><p>I figured a person from the company must have put that message on the board, but I had no idea who. So being the personable individual I am, I resolved not to ask a single real person about it. Instead, I messaged the company on Twitter.<\/p><p>They got back to me, tagging a list of people who were at the conference. They had done the job of introducing me to these people without me having to do it! Technology is truly magical sometimes. Except, they didn&rsquo;t introduce me to all those people. I already had breakfast with one of them earlier that day. The same person I failed to watch the hockey game with.<\/p><p>I spent the rest of the conference joining their group. Like I said, I&rsquo;m not adventurous. And I&rsquo;m very fortunate they let me tag along. One of them even performed later in the conference. No, they didn&rsquo;t give a talk. They gave a <a href=\"https:\/\/www.youtube.com\/watch?v=kRUS8Zvg3sg\">performance<\/a>.<\/p><p>As I mentioned, I found out about them from the jobs board. I wasn&rsquo;t looking for a job, but&hellip;this seemed like a good match. They were all really nice. The company used Rails. I wanted to get better at using Rails by working with others who knew more about it. I filled out the job application, and completed their code challenge, on the plane ride home. In 2015 I attended my second RailsConf - this time as an employee of that new company.<\/p><p><strong>Audience Takeaway<\/strong>: Extend yourself in ways that are unfamiliar, but not harmful to you. I am not good at meeting people, especially on my own. But here I was in a giant room full of like-minded people. And I was craving knowing more about how they work. So, I did the smallest thing that was still uncomfortable but brought me closer to that goal. And thanks to that, after the conference, things changed for me. I transitioned from having a job as a developer to having a career as a Rubyist. What are the ways you can safely extend yourself? Is it asking a friend to introduce you to someone new? Being interviewed by a podcast? Starting a podcast?!? Asking to interview someone for a podcast? Posting an open invitation to dinner, not know who might respond?<\/p><h2 id=\"expanding-my-impact-view\">Expanding My Impact (View) <a href=\"#expanding-my-impact-view\">\ud83d\udd17<\/a><\/h2><p>Seeing all those people on stage, sharing their knowledge and experience, inspired me. I wanted to do that. I felt like I had something to say&hellip;I just didn&rsquo;t know what that was. It seemed like a good way to have people come talk to me, so I didn&rsquo;t need to introduce myself to them. It became a good professional milestone.<\/p><p>One problem: I had no idea how to achieve it. Forget the talk delivery part. I had no idea how people got on stage. Were they personally asked? Was there a secret underground network of people trading slideshows? Did they rush the stage, getting there before anyone else could, laptop in hand?<\/p><p>Turns out, many conferences have public Calls for Proposals, or CFPs. Write a pitch about what you want to talk about, and they&rsquo;ll consider it for their conference. So I did. And failed. For years. But I kept trying. I tweaked proposals to send in again the next year. I submitted different ones. I learned from other people who have had proposals accepted in the past. In 2019, I received the familiar email that my RailsConf talk was not accepted. I was used to that, so it was fine. But something else made that email easier to read.<\/p><p>I already heard from RailsConf ten days earlier. They accepted a different proposal I also submitted!<\/p><p><strong>Audience Takeaway<\/strong>: A no isn&rsquo;t forever. And a no doesn&rsquo;t mean what you&rsquo;re offering is bad. Good talks, even great ones, get turned down every year. They&rsquo;re not the right mix for the program. There were 12 other proposals on the same topic. They ran out of space. There are so many reasons that talks don&rsquo;t get accepted. It&rsquo;s the same way with many other pursuits. If it&rsquo;s valuable to you, continue striving towards it. In this case, I\u2019m discussing a talk proposal. But this applies in many situations, like applying for jobs or making community connections.<\/p><p>I had experience writing a proposal, but now I needed to translate that into a talk on stage. I have given presentations before, but to me, nothing on this scale. That talk (and truthfully, every other one that has followed it too) took immense time and effort to deliver. I didn&rsquo;t want to disappoint the audience. They were choosing my session over other very worthy choices. I didn&rsquo;t want to disappoint the Program Committee. They had put their faith in me, and I didn&rsquo;t want to let them down. I didn&rsquo;t want to disappoint myself. I had waited years for this moment.<\/p><p>Speaking <em>was<\/em> the cheat code that I hoped it would be at a conference. You got to meet the other speakers beforehand. You had a different badge, so people could ask what you were talking about. After your session, people might come up to ask a question. They might give their perspective. Some might offer an encouraging word. A topic of conversation, without even having to try!<\/p><p>More important was how I felt afterwards. Sure, it was satisfying in the way that achieving a goal you had set years before should. But, it felt even better thinking that it may have benefited others. I was so focused on what it meant for me, I hadn&rsquo;t even considered it until after the fact.<\/p><p>After that, helping others to improve the community became a bigger driver for me. I started by doing what I knew: trying to speak at conferences. But, I started other things too. I wrote blog posts. I read other people&rsquo;s CFP submissions. I listened to early editions of presentations and gave them feedback. I made (small) commits to Ruby itself, based on conference talks I&rsquo;ve given. I guested on podcasts. I had the chance to serve on the Program Committee, ten years after my first RailsConf experience. It all had a selfish component, for sure. But what I got the most charge out of was hearing from other people. I relished hearing about what they learned, and what they enjoyed, about what I shared.<\/p><p><strong>Audience Takeaway<\/strong>: Appreciate the second-order benefits that come from your achievements. You may find they end up being much more valuable than your original goal. Find the ways that you can contribute to improve things for yourself and the world around you. Experiment with different media, or approaches. Keep what works for you and your group, and let go of what doesn&rsquo;t.<\/p><h2 id=\"building-my-support-system\">Building My Support System <a href=\"#building-my-support-system\">\ud83d\udd17<\/a><\/h2><p>#RubyFriends isn&rsquo;t just a hashtag, though it is a good one. It&rsquo;s a joyous public demonstration of the strength of the bonds in this community. I&rsquo;ve been able to meet a lot of people at RailsConf. I&rsquo;ve met a lot of people because of RailsConf. I&rsquo;ve met people in person whom I&rsquo;ve known (or at least known of) for some time for the first time at RailsConf.<\/p><p>Some of them live (near) my neighborhood. Some are across the world. But RailsConf literally brought us together. I&rsquo;ve already demonstrated that I&rsquo;ve met future coworkers and employers at RailsConf. My professional network has definitely expanded thanks to RailsConf.<\/p><p>I&rsquo;ve also met lifelong friends. I&rsquo;ve attended weddings of people I met at RailsConf, filling a table of other people I also met at RailsConf. I&rsquo;ve learned about my work and myself through the fellowship I have with these people. I&rsquo;m forever grateful for their company.<\/p><p><strong>Audience Takeaway<\/strong>: Take advantage of this time that we are together. Ask the person next to you at lunch what talks they saw today. Thank that maintainer for the work they do. Ask the volunteers and program staff if there&rsquo;s any help they need. It could change your life.<\/p><h2 id=\"continuing-the-benefits-controller\">Continuing The Benefits (Controller) <a href=\"#continuing-the-benefits-controller\">\ud83d\udd17<\/a><\/h2><p>With RailsConf as an event coming to a close, are we losing these opportunities? In some ways, the answer is yes. We&rsquo;re losing one chance for people to experience exactly these benefits I have. But, that doesn&rsquo;t mean it&rsquo;s over. For you, for me, or the community. We may need to work harder. We may need to create some of these chances to learn, to grow, to meet others, ourselves. Most importantly, I&rsquo;m saying we. It&rsquo;s up to you - but not individually. Collectively. We all have a role to play. For ourselves. For each other.<\/p><p>You can strengthen this community at home. It can be as small as a sentence or a few clicks. Tell the person who wrote the article you just read what you liked about it. Tell the person who recorded the podcast that accompanied your commute that you appreciated the conversation. Tell the person who compiles the newsletter you use to stay informed how it helps. Tell the person who uploaded the video that taught you that new thing how you&rsquo;re going to use it. And don&rsquo;t forget to like, subscribe, and ring that notification bell. Tell them all how meaningful their work and contribution was to you. Give them the encouragement to continue.<\/p><p>Look for online communities to build sustainable relationships. If you want to venture outside the home, look for a local meetup to join.<\/p><p>However, I know we don&rsquo;t need to go about this on our own. Ruby Central will still be a guiding force in the community, though their role may shift. I need feedback from Ruby Central to improve this section. I&rsquo;d like guidance about particular directives that Ruby Central will support. The obvious one is RubyConf. I&rsquo;ve also seen efforts to collect information about local meetups. How, if at all, will they support the development or execution of other Ruby conferences or events? What opportunities will there be for others to learn from other Rubyists? How can we work together to grow the community? What tangible actions do we want to highlight here? I\u2019ll also likely highlight other community groups, such as wnb.rb.<\/p><h1 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h1><p>I owe a tremendous debt to RailsConf and Ruby Central. My hope is to publicly celebrate and evangelize the good work they&rsquo;ve done. For myself and our community. It\u2019s premature to eulogize an event still going on, but I want to pay respect to the past that I\u2019ve been a part of. At the same time, I want the audience to leave not feeling like they missed out, but that there\u2019s still opportunity to engage going forward.<\/p><p>I&rsquo;ve attended many RailsConfs. I&rsquo;ve spoken at in-person and virtual RailsConfs. I&rsquo;ve done CFP coaching sessions for multiple RailsConfs. I&rsquo;ve served on a RailsConf Program Committee.<\/p><p>Thank you Ruby Central. Thank you for RailsConf. Thanks to everyone who ever attended a RailsConf. You made it all worth it. Now it&rsquo;s up to us all to keep the momentum of the community going.<\/p><h1 id=\"additional-notes\">Additional Notes <a href=\"#additional-notes\">\ud83d\udd17<\/a><\/h1><h1 id=\"speaker\">Speaker <a href=\"#speaker\">\ud83d\udd17<\/a><\/h1><p>Kevin Murphy<\/p><p>Software Developer @ Pubmark<\/p><p>Kevin lives near Boston, Massachusetts, US where he is a Software Developer at Pubmark. He&rsquo;s attended 7 RailsConfs in some capacity. He wishes that number was higher.<\/p>"},{"title":"Frequently Played June 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-06\/","pubDate":"Wed, 04 Jun 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-06\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"countin-on-a-miracle\">Countin&rsquo; On A Miracle <a href=\"#countin-on-a-miracle\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/WDuSvI6ym0Q?si=pffnZkI7DjQZNAVn\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Unfortunately, miracles are nothing more than <a href=\"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-12\/#the-river\">dreams gone bad<\/a>.<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-countin-on-a-miracle-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>It&rsquo;s a fairy tale so tragic<br>There&rsquo;s no prince to break the spell<br>I don&rsquo;t believe in magic<br>But for you, I will, yeah, for you, I will<br>If I&rsquo;m a fool, I&rsquo;ll be a fool<br>Darling, for you<\/p><\/blockquote><h2 id=\"could-have-been-me\">Could Have Been Me <a href=\"#could-have-been-me\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/7KrCCmXjUqs?si=0GMJ5mNIdWJp0UnN\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>The Struts are maybe the greatest expression of pure joy and excitement I&rsquo;veseen live. If Rock N Roll still mattered culturally, they&rsquo;d be a household name.<\/p><p><a href=\"https:\/\/genius.com\/The-struts-could-have-been-me-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I wanna live better days<br>Never look back and say<br>Could have been me<br>It could have been me<\/p><\/blockquote><h2 id=\"yvette-young\">Yvette Young <a href=\"#yvette-young\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/_Q1-ETuBg5A?si=RJDWEJDQY3Jc_eks\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Yvette is the guitarist in Covet and has solo stuff too. I&rsquo;ve mostly been enjoyingher interviews. Her playing style is phenomenal, and I&rsquo;m intrigued by howshe approaches the instrument. I like hearing the way she explains concepts anddescribes how she makes the choices she does.<\/p>"},{"title":"Rails Controller Callback Order With Concerns","link":"https:\/\/kevinjmurphy.com\/posts\/rails-controller-callback-order-with-concerns\/","pubDate":"Fri, 16 May 2025 20:53:21 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rails-controller-callback-order-with-concerns\/","description":"<h2 id=\"how-can-i-help-you-today\">How can I help you today? <a href=\"#how-can-i-help-you-today\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re building an app to display the prompts from an automated phone system. The system will read these out when people call our technical support phone number. We use a <a href=\"https:\/\/guides.rubyonrails.org\/action_controller_overview.html#controller-callbacks\">callback<\/a> to set the list of prompts to read. Whether you&rsquo;re calling for details about consuming our GraphQL endpoints or looking for tips on charging your phone battery, you&rsquo;re going to hear these messages first.<\/p><p>Our controller action renders that list.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TechSupportPromptsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">show<\/span><\/span><\/span><span style=\"display:flex;\"><span> render <span style=\"color:#e6db74\">inline<\/span>: <span style=\"color:#e6db74\">&#34;&lt;%= @prompts.join(&#39; - &#39;) %&gt;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Thank you for calling technical support.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Call volume is higher than expected.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Press 9 to receive a call back when an agent is available.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>If you&rsquo;re curious about avoiding a view for rendering, you can read more about it in the <a href=\"https:\/\/guides.rubyonrails.org\/layouts_and_rendering.html#using-render-with-inline\">Rails Guides<\/a>. Pay attention to the part where it advises against using it in most cases! I&rsquo;m doing it here for brevity, which I&rsquo;m now diminishing the value of by spending a paragraph explaining it.<\/p><h2 id=\"making-a-test-call\">Making a test call <a href=\"#making-a-test-call\">\ud83d\udd17<\/a><\/h2><p>We want to make sure that this is working as expected, so let&rsquo;s write a test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;displays the prompts in the proper order&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> get tech_support_prompt_path<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response<span style=\"color:#f92672\">.<\/span>body<span style=\"color:#f92672\">.<\/span>split(<span style=\"color:#e6db74\">&#34; - &#34;<\/span>))<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Thank you for calling technical support.&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Call volume is higher than expected.&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Press 9 to receive a call back when an agent is available.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This test passes, so we ship it.<\/p><h2 id=\"sharing-the-call-party-line\">Sharing the call (Party Line) <a href=\"#sharing-the-call-party-line\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re preparing for our next feature. We will display the prompts for our general customer support phone number. After reading the requirements, we see some consistency. When calling tech support or customer support, we end by telling the caller how to request a callback.<\/p><p>Before starting on this, we&rsquo;re going to prepare the tech support display for some reuse. We want to share the code that adds these common prompts at the end of the interaction. As a starting point, we separate these later prompts to a separate method. We then invoke it in a separate before action.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TechSupportPromptsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">show<\/span><\/span><\/span><span style=\"display:flex;\"><span> render <span style=\"color:#e6db74\">inline<\/span>: <span style=\"color:#e6db74\">&#34;&lt;%= @prompts.join(&#39; - &#39;) %&gt;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Your call is important to us.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Call volume is higher than expected.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Press 9 to receive a call back when an agent is available.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our test passes, because these callbacks are <a href=\"https:\/\/reganchan.ca\/blog\/ordering-of-filters-in-rails-controllers\/\">invoked in the order in which they&rsquo;re written<\/a>.<\/p><blockquote><p>When using <code>before_action<\/code>, the filters are called in the order that they are defined<\/p><\/blockquote><p>Our next step to reuse these is to move these common prompts into a <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActiveSupport\/Concern.html\">concern<\/a>. This will allow us to access the prompts in different controllers. Even better, we surmise, the concern itself can define the <code>before_action<\/code>. Now all the controller needs to do is include the concern.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Callbackable<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">extend<\/span> <span style=\"color:#66d9ef\">ActiveSupport<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Concern<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> included <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Call volume is higher than expected.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Press 9 to receive a call back when an agent is available.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>And now we include the concern in the controller.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TechSupportPromptsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Callbackable<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Running our test doesn&rsquo;t pass. Instead it raises an exception!<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>NoMethodError:<\/span><\/span><span style=\"display:flex;\"><span> undefined method <span style=\"color:#e6db74\">`<\/span>&lt;&lt;<span style=\"color:#e6db74\">&#39; for nil<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\"> # .\/app\/controllers\/concerns\/callbackable.rb:6:in `block (2 levels) in &lt;module:Callbackable&gt;&#39;<\/span><\/span><\/span><\/code><\/pre><\/div><p>The concern is attempting to shovel a message onto a variable that is currently <code>nil<\/code>. <code>nil<\/code> does not respond to <code>&lt;&lt;<\/code>. The <code>@prompts<\/code> variable is not initialized at this point. The rule about callback order applies here for the concern as well.<\/p><p>We include the concern before calling the callback which initializes <code>@prompts<\/code>. Therefore <code>@prompts<\/code> has no value.<\/p><p>We can correct for that by including the concern after the callback.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TechSupportPromptsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Callbackable<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our test passes again.<\/p><h2 id=\"calling-our-taste-into-question\">Calling our taste into question <a href=\"#calling-our-taste-into-question\">\ud83d\udd17<\/a><\/h2><p>That may not sit well with you. Maybe it offends your sensibilities. Perhaps there are other include statements for your controller. You may want to put them all together, right after defining the class for the controller. However, the order of this include is important. It <em>must<\/em> be after the other <code>before_action<\/code>.<\/p><p>Even if you&rsquo;re totally fine with the include being anywhere, it&rsquo;s unclear that it needs to be after the callback. Luckily we have a test that will catch if someone inadvertently moves it. Even with the test, it&rsquo;s not obvious that the ordering is important. We have no idea what the concern does without looking at it. We don&rsquo;t know there&rsquo;s a callback running in it.<\/p><p>Alternatively, we can modify the concern to not specify the callback. It still defines the <code>add_callback_notices<\/code> method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Callbackable<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">extend<\/span> <span style=\"color:#66d9ef\">ActiveSupport<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Concern<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Call volume is higher than expected.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @prompts <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;Press 9 to receive a call back when an agent is available.&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>In our controller, we have access to the method by including the concern. However, it&rsquo;s again our responsibility to call it. We do so by adding a second callback, just like we did when this method existed in the controller.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TechSupportPromptsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Callbackable<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:set_prompts<\/span><\/span><\/span><span style=\"display:flex;\"><span> before_action <span style=\"color:#e6db74\">:add_callback_notices<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>In this scenario, we still need to know the rule about what order Rails applies these callbacks. I personally find it more clear to see the callback defined in the controller anyway. It also gives you the freedom to include the concern anywhere. Perhaps with the rest of the modules you&rsquo;re including! You don&rsquo;t need to be concerned with the order of includes.<\/p><p>Give me <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">a call<\/a> to let me know what you think.<\/p>"},{"title":"Frequently Played May 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-05\/","pubDate":"Mon, 05 May 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-05\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"de-profundis-borealis\">De Profundis Borealis <a href=\"#de-profundis-borealis\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/8ixwcLp5ZU4?si=RXpWZtBuzvI7vDIf\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Ghost dropped a new psalm (album) at the end of April, so that&rsquo;s been my focus.<\/p><p><a href=\"https:\/\/genius.com\/Ghost-de-profundis-borealis-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>All the words that never came out right<br>Within reach, but out of sight<br>All the songs that couldn&rsquo;t mend your heart<br>But slit it up and tore apart<\/p><\/blockquote><h2 id=\"lachryma\">Lachryma <a href=\"#lachryma\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/DLG9oTH-ZbQ?si=hl_dB-OachGl6zAa\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I already told you: new Ghost.<\/p><p><a href=\"https:\/\/genius.com\/Ghost-lachryma-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>In the middle of th\u0435 night, it feeds<br>In the middle of the night, it \u0435ats you<br>Everybody knows<br>Everywhere I go<br>I can never run and I cannot hide<\/p><p>I&rsquo;m done crying<br>Over someone like you<br>I&rsquo;m done crying<br>Hope you&rsquo;re feeling it too now<br>I&rsquo;m done<\/p><\/blockquote>"},{"title":"InstiLLMent of Successful Practices in an Agentic World - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/instillment-of-successful-practices-in-an-agentic-world-proposal\/","pubDate":"Mon, 28 Apr 2025 05:26:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/instillment-of-successful-practices-in-an-agentic-world-proposal\/","description":"<h1 id=\"elevator-pitch\">Elevator Pitch <a href=\"#elevator-pitch\">\ud83d\udd17<\/a><\/h1><p>Let\u2019s leverage the expectations that developers have interacting with LLMs to receive a satisfactory output. We will highlight that through the lens of how we can manage humans and ourselves. Rubyists use a language which promotes developer happiness. Our processes and systems should too.<\/p><h1 id=\"description\">Description <a href=\"#description\">\ud83d\udd17<\/a><\/h1><p>Congrats on joining Hours Unlimited. The Math and Numbers team is excited to have you join us on our journey to redefine the importance of numerals. This introductory session will provide tips and tricks for best interacting with powerfuLLMachine, the next-level platform we use to unlock productivity and effectiveness.<\/p><h1 id=\"notes\">Notes <a href=\"#notes\">\ud83d\udd17<\/a><\/h1><p>This talk will begin by setting the stage for the audience. They are all attending orientation for a new role where they will be interacting with a Large Language Model developed in-house by the organization: powerfuLLMachine. In this brave new world, rather than removing vowels from company or product names, we&rsquo;re <em>adding<\/em> consonants.<\/p><h2 id=\"interacting-with-the-llm\">Interacting with the LLM <a href=\"#interacting-with-the-llm\">\ud83d\udd17<\/a><\/h2><h3 id=\"prompting\">Prompting <a href=\"#prompting\">\ud83d\udd17<\/a><\/h3><p>As an introduction, we begin by prompting powerfuLLMachine with targeted requests for output. We learn that the smaller we are able to break the problem down, the more predictable results we receive. We observe how specificity and clarity are critical in reducing the feedback loop. We are willing to throw away results that don\u2019t match our expectations. We experiment with alternative implementations. We aren\u2019t precious with the code that\u2019s output while we\u2019re in the development phase.<\/p><h3 id=\"agentic\">Agentic <a href=\"#agentic\">\ud83d\udd17<\/a><\/h3><p>Taking this a step further, we can provide powerfuLLMachine with more autonomy. Given input requesting a higher-level output, such as a whole feature, we leave the system alone to generate a result. powerfuLLMachine will submit a patch with a review request to our source code management system when it\u2019s done, tagging you as a reviewer.<\/p><p>When asking powerfuLLMachine to complete the task, we must provide a detailed description. It needs to explain what is known in the result we want and where any wiggle room may exist in how it may interpret the instructions.<\/p><p>To reduce repetitive prompting, our code repository has a set of markdown files that powerfuLLMachine knows to ingest prior to any work.<\/p><h4 id=\"backgroundmd\">background.md <a href=\"#backgroundmd\">\ud83d\udd17<\/a><\/h4><p>In this file, we identify the qualifications we expect powerfuLLMachine to have and use to complete the task. This can be a paragraph description or bullet points. Examples include:<\/p><ul><li>Senior developer with 5-10 years of experience with Ruby on Rails<\/li><li>Familiar with TDD<\/li><li>Uses Postgres<\/li><li>Prefers using Turbo over other JavaScript interactions<\/li><\/ul><h4 id=\"motivationmd\">motivation.md <a href=\"#motivationmd\">\ud83d\udd17<\/a><\/h4><p>Here we provide a more conversational tone to demonstrate what is driving the system to achieve its goal. It cares about deeply testing functionality, but is mindful of the impact that tests remaining in the regression suite have on our Continuous Delivery system.<\/p><p>We share how it values system design with modularity that increases the number of options we retain while adding features. It is looking to be more aware of existing functionality in the code base, as well as in its dependencies, rather than building something from the ground-up.<\/p><h4 id=\"guardrailsmd\">guardrails.md <a href=\"#guardrailsmd\">\ud83d\udd17<\/a><\/h4><p>This file contains a very important list of instructions that powerfuLLMachine knows it absolutely must follow. This includes statements like:<\/p><ul><li>No credentials or secret values may be committed in any patches.<\/li><li>All changes must include tests demonstrating the behavior implemented.<\/li><li>Do not connect to any deployed environment while developing (don\u2019t ask what led us to need to add that one in).<\/li><\/ul><h4 id=\"preferencesmd\">preferences.md <a href=\"#preferencesmd\">\ud83d\udd17<\/a><\/h4><p>This is the most squishy of our inputs. There are standards we like to live by. They should be our default operating model. However, we recognize that sometimes different approaches are necessary.<\/p><ul><li>We prefer low-level unit tests over full-scale feature and system tests. However, not at the cost of excessive mocking that\u2019s unrealistic for how the units will be used.<\/li><li>We value detailed commit messages that explain the why behind the change and explain other options that were considered.<\/li><li>We like shipping small, atomic units that can be reviewed and delivered quickly.<\/li><li>When a large change is necessary, consider a \u201cstacked diff\u201d delivery so that reviews can be targeted, even if they\u2019re merged together or in quick succession.<\/li><\/ul><h3 id=\"reviewing-agentic-coding\">Reviewing Agentic Coding <a href=\"#reviewing-agentic-coding\">\ud83d\udd17<\/a><\/h3><p>We wrap up by sharing expectations for how we expect developers to interact with the code that our agentic powerfuLLMachine delivers for review. It is important to review any patches quickly. It\u2019s likely that more revisions will be necessary, so we should kick off that process as early as possible. We wipe the context of any agentic runners on a regular basis, but retain them for a short period of time for the review phase. We also have limited compute power granted to powerfuLLMachine agents once it delivers code for review. Therefore, we should be very clear about when feedback is required or they are optional suggestions. The system will make an attempt to deliver on suggestions but will balance this against available compute resources in an intelligent fashion.<\/p><p>We also highlight the importance of more qualitative feedback. Praise and positive reactions to particular results, including the reason why it\u2019s appreciated, is important. That feedback will be pushed back into the training corpus for powerfuLLMachine to be used in future changes. Asking a question will cause powerfuLLMachine to reach back into its context to justify choices made, share resources used in making a determination, and highlighting other approaches that could be used, along with their trade-offs.<\/p><h2 id=\"recontextualizing\">Recontextualizing <a href=\"#recontextualizing\">\ud83d\udd17<\/a><\/h2><p>At this point, we will step outside of our experience at Hours Unlimited for a dramatic reveal: powerfuLLMachine does not exist. At least, not as a product. However, we can reflect on how having such a system could be desirable. And how the work we\u2019ve done to set up our interactions with the system are worthwhile.<\/p><p>Even without powerfuLLM, we can, and should have these benefits.<\/p><p><strong>H<\/strong> ours<\/p><p><strong>U<\/strong> nlimited (our fictional company)<\/p><p><strong>M<\/strong> ath<\/p><p><strong>A<\/strong> nd<\/p><p><strong>N<\/strong> umbers (our fictional department)<\/p><p>HUMAN. We, as humans, deserve the same consideration we\u2019ve been providing to this computer. All of this is for humans. Not text prediction machines. Humans have context and memory. We should embrace this strategy for the humans on our teams before the computers we work on. We should apply Ruby\u2019s ethos of developer happiness to benefit our developers above the tools they\u2019re using to complete their tasks.<\/p><h3 id=\"active-development\">Active Development <a href=\"#active-development\">\ud83d\udd17<\/a><\/h3><p>We can take the experience we had with prompting powerfuLLMachine step-by-step to inform how we develop changes. The smaller the surface area of the problem we\u2019re solving, while also armed with the entire goal, the more focus we can provide. If we don\u2019t have details about the problem we\u2019re trying to solve, we should seek that out from our team or through research.<\/p><p>We shouldn\u2019t accept that the first approach we take is the best one in all situations. We need to be willing to put it aside and try again, but informed by what we learned from that initial instinct. What was difficult that could be improved? What did we like that we want to continue? We can explore and play with options. It could, dare I say, even be fun.<\/p><h3 id=\"expectations\">Expectations <a href=\"#expectations\">\ud83d\udd17<\/a><\/h3><p>All the work to set our agentic coding models up for success isn\u2019t lost. This is valuable information that should be shared and documented. When setting out on feature development, we need to know what success looks like. What are we willing to live with, and where can we be flexible?<\/p><p>All the documents we generated for our LLM have value for all of our humans as well. We as individuals are agents. Even more capable of agency than our LLM.<\/p><p>background.md == Job Descriptionmotivation.md == Career Goals and Growth Opportunitiesguardrails.md == Policies and Procedurespreferences.md == Team Norms and Expectations<\/p><p>These can, and should be, written out and readily available for all team members. They should be up for debate and modification, much like you would propose a change to these markdown files for a LLM.<\/p><h3 id=\"reviewing-code\">Reviewing Code <a href=\"#reviewing-code\">\ud83d\udd17<\/a><\/h3><p>These tips and tricks are all valuable for human beings! Work presented for review is either believed to be close to shipping or blocked and needs help. As such, it should be reacted to in a timely fashion. Authors can forget details in their thought process as time goes on.<\/p><p>Authors have moved on to other work. Their capacity to re-engage with what they\u2019ve delivered may be limited. Be clear about how the code should change so it can be merged and why.<\/p><p>Focusing only on what should change is a missed opportunity. Highlight what works well in the code that\u2019s presented. Congratulate authors for the work they\u2019ve done and voice your appreciation. Ask questions to gain insight on how this change was made. Learn from each other. Develop a shared understanding of how you all like to work. That has benefits not only for the change in question. It\u2019s crucial in forming a long-lasting working relationship.<\/p><p>We\u2019re willing to provide all of this for our automated development tools; why not ourselves?<\/p><h3 id=\"tags\">Tags <a href=\"#tags\">\ud83d\udd17<\/a><\/h3><p>Ruby, Professional Development, Career Growth<\/p>"},{"title":"Speaking at RailsConf 2025","link":"https:\/\/kevinjmurphy.com\/posts\/speaking-at-railsconf-2025\/","pubDate":"Thu, 10 Apr 2025 16:14:31 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/speaking-at-railsconf-2025\/","description":"<h2 id=\"railsconf-2025\">RailsConf 2025 <a href=\"#railsconf-2025\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m honored to be <a href=\"https:\/\/railsconf.org\/speakers\/#kevin-murphy\">speaking<\/a> at the final <a href=\"https:\/\/railsconf.org\/\">RailsConf<\/a> this July in Philadelphia.I&rsquo;ll be speaking on Thursday, July 10 at 10:15am on the Main Stage in theLiberty room.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_speaker_profile.png\" alt=\"Kevin lives near Boston, Massachusetts, US where he is a Software Developer at Pubmark. He&#39;s attended 7 RailsConfs in some capacity. He wishes that number was higher.\"><\/figure><h2 id=\"how-10-years-of-railsconfs-can-inform-the-next-10-years-of-your-career\">How 10 years of RailsConfs can inform the next 10 years of your career <a href=\"#how-10-years-of-railsconfs-can-inform-the-next-10-years-of-your-career\">\ud83d\udd17<\/a><\/h2><p>RailsConf has played an important role in my professional and personal life. I&rsquo;ve learned about technology in ways I wouldn&rsquo;t have otherwise at RailsConf. I&rsquo;ve learned about myself in ways I wouldn&rsquo;t have otherwise thanks to RailsConf. I&rsquo;ve met people that changed the course of my career thanks to RailsConf. I&rsquo;ve met others who have become dear friends thanks to RailsConf. I&rsquo;ve done things I&rsquo;ve never done before thanks to RailsConf.<\/p><p>The same may be true for you. We should celebrate and reflect on that. Where do we go from here? Let&rsquo;s talk about it together.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2025_banner.jpeg\" alt=\"How 10 years of RailsConfs can inform the next 10 years of your career\"><\/figure>"},{"title":"Frequently Played April 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-04\/","pubDate":"Mon, 07 Apr 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-04\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"racing-in-the-street\">Racing In The Street <a href=\"#racing-in-the-street\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/8j1aCBRWlc8?si=JbVT8eb_jz9lGOkm\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This musical outro is transcendent.<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-racing-in-the-street-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>She sits on the porch of her daddy&rsquo;s house<br>But all her pretty dreams are torn<br>She stares off alone into the night<br>With the eyes of one who hates for just being born<\/p><\/blockquote><h2 id=\"snuff\">Snuff <a href=\"#snuff\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/PvB6QApisk8?si=pyj2hyMvsQpIe0vv\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I&rsquo;m not super familiar with Slipknot, but I know others are. Jim Root seemslike a really cool, down-to-Earth guitar playing person. At the time of thisperformance, that&rsquo;s Jay Weinberg playing drums, son of Max, the drummer of the EStreet Band. Jay has filled in for Max on E Street dates in the past.<\/p><p><a href=\"https:\/\/genius.com\/Slipknot-snuff-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>You ran away, you&rsquo;re all the same<br>Angels lie to keep control<br>My love was punished long ago<\/p><\/blockquote>"},{"title":"Preserving Flash Messages in Rails","link":"https:\/\/kevinjmurphy.com\/posts\/preserving-flash-messages-in-rails\/","pubDate":"Fri, 14 Mar 2025 08:00:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/preserving-flash-messages-in-rails\/","description":"<h2 id=\"flash-sale\">Flash Sale! <a href=\"#flash-sale\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re offering our best deals on select products for a limited time. We&rsquo;re going to link to this flash sale from many different pages on our site. The call-to-action (CTA) we display at the top of the flash sale page will change based on which page you access the flash sale from.<\/p><p>We&rsquo;re going to store that text in a flash message. For example, let&rsquo;s say you&rsquo;re looking for the contact information for everyone at the shop. If you click a link on that page to visit the flash sale, we reference that you were just on the contact page.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ContactsController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">index<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#66d9ef\">FlashSale<\/span><span style=\"color:#f92672\">.<\/span>on?<\/span><\/span><span style=\"display:flex;\"><span> flash<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:sale<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Thanks to you for looking to contact us&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Keep in mind this sale may be over in&hellip;a flash. In between loading the contacts page and clicking the link to view the flash sale, it may be over. If that&rsquo;s the case, we still want to show the CTA. However, we&rsquo;ll send you to the general products page, rather than the flash sale page.<\/p><p>We sample some of our requests to log information about them. If we&rsquo;re sampling this request, and they&rsquo;re getting redirected because the flash sale is over, we want to log what CTA brought them there. That way we can figure out what page they were on.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FlashSalesController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">index<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#66d9ef\">FlashSale<\/span><span style=\"color:#f92672\">.<\/span>off?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#66d9ef\">AttemptLogger<\/span><span style=\"color:#f92672\">.<\/span>log?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>info <span style=\"color:#e6db74\">&#34;CTA &#39;<\/span><span style=\"color:#e6db74\">#{<\/span>flash<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:sale<\/span><span style=\"color:#f92672\">]<\/span><span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#39; used to access flash sale after it&#39;s over&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> redirect_to products_path <span style=\"color:#f92672\">and<\/span> <span style=\"color:#66d9ef\">return<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @products <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> donner_red_hss_starter_kit,<\/span><\/span><span style=\"display:flex;\"><span> martin_junior_acoustic,<\/span><\/span><span style=\"display:flex;\"><span> squier_affinity_strat_junior_hss,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The view for the products page displays the flash message. We still have the experience of referencing the page they came from. Even if it isn&rsquo;t our best deals.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>&lt;h1&gt;All Products&lt;\/h1&gt;<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>&lt;% <span style=\"color:#66d9ef\">if<\/span> flash<span style=\"color:#f92672\">[<\/span>:sale<span style=\"color:#f92672\">]<\/span>.present? %&gt;<\/span><\/span><span style=\"display:flex;\"><span> &lt;p&gt; &lt;%<span style=\"color:#f92672\">=<\/span> flash<span style=\"color:#f92672\">[<\/span>:sale<span style=\"color:#f92672\">]<\/span> %&gt; &lt;\/p&gt;<\/span><\/span><span style=\"display:flex;\"><span>&lt;% end %&gt;<\/span><\/span><\/code><\/pre><\/div><h2 id=\"gone-in-a-flash\">Gone In A Flash <a href=\"#gone-in-a-flash\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s confirm this behavior by writing some <a href=\"https:\/\/guides.rubyonrails.org\/testing.html#system-testing\">system tests<\/a>. We&rsquo;ll start by verifying the redirect when the flash sale ends before they can view the deals.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;redirects to the products page with the flash message when the flash sale has ended&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">FlashSale<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:on?<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> visit contacts_path<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">FlashSale<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:off?<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> click_link <span style=\"color:#e6db74\">&#34;View Deals&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(page)<span style=\"color:#f92672\">.<\/span>to have_selector <span style=\"color:#e6db74\">&#34;h1&#34;<\/span>, <span style=\"color:#e6db74\">text<\/span>: <span style=\"color:#e6db74\">&#34;All Products&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(page)<span style=\"color:#f92672\">.<\/span>to have_content <span style=\"color:#e6db74\">&#34;Thanks to you for looking to contact us&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our test passes!<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>\u21d2 rspec spec\/system\/flash_sales_spec.rb:8<\/span><\/span><span style=\"display:flex;\"><span>Run options: include <span style=\"color:#f92672\">{<\/span>:locations<span style=\"color:#f92672\">=<\/span>&gt;<span style=\"color:#f92672\">{<\/span><span style=\"color:#e6db74\">&#34;.\/spec\/system\/flash_sales_spec.rb&#34;<\/span><span style=\"color:#f92672\">=<\/span>&gt;<span style=\"color:#f92672\">[<\/span>8<span style=\"color:#f92672\">]}}<\/span><\/span><\/span><span style=\"display:flex;\"><span>.<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>Finished in 0.12213 seconds <span style=\"color:#f92672\">(<\/span>files took 1.89 seconds to load<span style=\"color:#f92672\">)<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#ae81ff\">1<\/span> example, <span style=\"color:#ae81ff\">0<\/span> failures<\/span><\/span><\/code><\/pre><\/div><p>Next we&rsquo;ll iterate on this by verifying the same user behavior when we log the CTA.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;logs the flash message when the flash sale has ended and the log sampler wants the message&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">FlashSale<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:on?<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> visit contacts_path<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">FlashSale<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:off?<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">AttemptLogger<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:log?<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> click_link <span style=\"color:#e6db74\">&#34;View Deals&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(page)<span style=\"color:#f92672\">.<\/span>to have_selector <span style=\"color:#e6db74\">&#34;h1&#34;<\/span>, <span style=\"color:#e6db74\">text<\/span>: <span style=\"color:#e6db74\">&#34;All Products&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(page)<span style=\"color:#f92672\">.<\/span>to have_content <span style=\"color:#e6db74\">&#34;Thanks to you for looking to contact us&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Unfortunately, we have a different result here.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>\u21d2 rspec spec\/system\/flash_sales_spec.rb:17<\/span><\/span><span style=\"display:flex;\"><span>Run options: include <span style=\"color:#f92672\">{<\/span>:locations<span style=\"color:#f92672\">=<\/span>&gt;<span style=\"color:#f92672\">{<\/span><span style=\"color:#e6db74\">&#34;.\/spec\/system\/flash_sales_spec.rb&#34;<\/span><span style=\"color:#f92672\">=<\/span>&gt;<span style=\"color:#f92672\">[<\/span>17<span style=\"color:#f92672\">]}}<\/span><\/span><\/span><span style=\"display:flex;\"><span>F<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>Failures:<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> 1<span style=\"color:#f92672\">)<\/span> Flash Sale logs the flash message when the flash sale has ended and the log sampler wants the message<\/span><\/span><span style=\"display:flex;\"><span> Failure\/Error: expect<span style=\"color:#f92672\">(<\/span>page<span style=\"color:#f92672\">)<\/span>.to have_content <span style=\"color:#e6db74\">&#34;As thanks to you for looking to contact us&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> expected to find text <span style=\"color:#e6db74\">&#34;As thanks to you for looking to contact us&#34;<\/span> in <span style=\"color:#e6db74\">&#34;All Products&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># .\/spec\/system\/flash_sales_spec.rb:26:in `block (2 levels) in &lt;top (required)&gt;&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>Finished in 0.21189 seconds <span style=\"color:#f92672\">(<\/span>files took 1.98 seconds to load<span style=\"color:#f92672\">)<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#ae81ff\">1<\/span> example, <span style=\"color:#ae81ff\">1<\/span> failure<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>Failed examples:<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>rspec .\/spec\/system\/flash_sales_spec.rb:17 <span style=\"color:#75715e\"># Flash Sale logs the flash message when the flash sale has ended and the log sampler wants the message<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our flash message is nowhere to be found. For an almost identical test. After <a href=\"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/\">searching<\/a> through the documentation, we find <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActionDispatch\/Flash.html\">this<\/a>:<\/p><blockquote><p>Anything you place in the flash will be exposed to the very next action and then cleared out.<\/p><\/blockquote><p>That leaves us even <em>more<\/em> confused. Our first test passed, even though we went from the flash sale endpoint to the products endpoint. Is that two actions? And if not, why did it clear it before exposing it to the user when we log the message?<\/p><h2 id=\"viewing-source-in-a-flash\">Viewing Source In A Flash <a href=\"#viewing-source-in-a-flash\">\ud83d\udd17<\/a><\/h2><p>That documentation tells us where to find the implementation for the flash: <code>actionpack\/lib\/action_dispatch\/middleware\/flash.rb<\/code>. We can use <a href=\"https:\/\/bundler.io\/man\/bundle-open.1.html\">bundle open<\/a> to explore the source code.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>$ bundle open actionpack<\/span><\/span><\/code><\/pre><\/div><p>We navigate to the flash file and look around. In there we see a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L71\">method<\/a> called <code>commit_flash<\/code>. Based only on the name, we guess that might have something to do with keeping the values of the flash. In one of our tests, we aren&rsquo;t keeping the values. Did we find a bug in Rails?<\/p><p>We drop a breakpoint in the method, changing Rails&rsquo; source code (temporarily) for our application. Then we can run our tests again to investigate.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">commit_flash<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">+<\/span> binding<span style=\"color:#f92672\">.<\/span>irb <span style=\"color:#75715e\"># Added by us!<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> <span style=\"color:#66d9ef\">unless<\/span> session<span style=\"color:#f92672\">.<\/span>enabled?<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> flash_hash <span style=\"color:#f92672\">&amp;&amp;<\/span> (flash_hash<span style=\"color:#f92672\">.<\/span>present? <span style=\"color:#f92672\">||<\/span> session<span style=\"color:#f92672\">.<\/span>key?(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>))<\/span><\/span><span style=\"display:flex;\"><span> session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> flash_hash<span style=\"color:#f92672\">.<\/span>to_session_value<\/span><\/span><span style=\"display:flex;\"><span> self<span style=\"color:#f92672\">.<\/span>flash <span style=\"color:#f92672\">=<\/span> flash_hash<span style=\"color:#f92672\">.<\/span>dup<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> session<span style=\"color:#f92672\">.<\/span>loaded? <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">.<\/span>key?(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>) <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">].<\/span>nil?<\/span><\/span><span style=\"display:flex;\"><span> session<span style=\"color:#f92672\">.<\/span>delete(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"a-flash-in-the-pan\">A Flash In The Pan <a href=\"#a-flash-in-the-pan\">\ud83d\udd17<\/a><\/h2><p>We observe the following behavior when running our tests:<\/p><ol><li>When we don&rsquo;t log the message in the <code>FlashSalesController#index<\/code> method, the value of <code>flash_hash<\/code> in <code>commit_flash<\/code> is <code>nil<\/code> at the end of processing the flash sale index action.<\/li><li>When we <em>do<\/em> log the message, <code>flash_hash<\/code> has a value.<\/li><\/ol><p>That still seems backwards from the behavior we&rsquo;re seeing. When we try to show the flash message on the products page, it&rsquo;s not there when we log the message. But that&rsquo;s the scenario where <code>flash_hash<\/code> has a value. Now we need to take a detour to understand where this <code>flash_hash<\/code> comes from. We find it&rsquo;s a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L67\">method<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">flash_hash<\/span><\/span><\/span><span style=\"display:flex;\"><span> get_header <span style=\"color:#66d9ef\">Flash<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">KEY<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>That leads us to wonder what can <em>set<\/em> that header. And we see <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L63\">just above<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">flash<\/span><span style=\"color:#f92672\">=<\/span>(flash)<\/span><\/span><span style=\"display:flex;\"><span> set_header <span style=\"color:#66d9ef\">Flash<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">KEY<\/span>, flash<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>As our eyes continue to look up, we find a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L57\">method<\/a> that calls <code>flash=<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">flash<\/span><\/span><\/span><span style=\"display:flex;\"><span> flash <span style=\"color:#f92672\">=<\/span> flash_hash<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> flash <span style=\"color:#66d9ef\">if<\/span> flash<\/span><\/span><span style=\"display:flex;\"><span> self<span style=\"color:#f92672\">.<\/span>flash <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Flash<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">FlashHash<\/span><span style=\"color:#f92672\">.<\/span>from_session_value(session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This feels a bit circular in the beginning. We look to see if <code>flash_hash<\/code> is set and return that if so. But we&rsquo;re looking here to see how <code>flash_hash<\/code> could get a value. It is the <em>last<\/em> line that&rsquo;s setting the header that <code>flash_hash<\/code> uses. So, that&rsquo;s called when <code>flash_hash<\/code> doesn&rsquo;t have a value. The flash object is grabbed from the session and converted to a <code>FlashHash<\/code>.<\/p><p>That will store that <code>FlashHash<\/code> instance in the header. We can then access that by calling the <code>flash_hash<\/code> method.<\/p><p>We also see a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L54C9-L54C86\">comment<\/a> above the <code>flash<\/code> method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Access the contents of the flash. Returns a ActionDispatch::Flash::FlashHash.<\/span><\/span><\/span><\/code><\/pre><\/div><p><em>This<\/em> is what we&rsquo;re calling when we call <code>flash[:sale]<\/code> in our logging message. <code>FlashHash<\/code> defines the <code>[]<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L169\">method<\/a>.<\/p><h2 id=\"flash-back\">Flash Back <a href=\"#flash-back\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s revisit each conditional in <code>commit_flash<\/code> and explore how they relate to our test scenarios.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">if<\/span> flash_hash <span style=\"color:#f92672\">&amp;&amp;<\/span> (flash_hash<span style=\"color:#f92672\">.<\/span>present? <span style=\"color:#f92672\">||<\/span> session<span style=\"color:#f92672\">.<\/span>key?(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>))<\/span><\/span><span style=\"display:flex;\"><span> session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> flash_hash<span style=\"color:#f92672\">.<\/span>to_session_value<\/span><\/span><span style=\"display:flex;\"><span> self<span style=\"color:#f92672\">.<\/span>flash <span style=\"color:#f92672\">=<\/span> flash_hash<span style=\"color:#f92672\">.<\/span>dup<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When this is called after the <code>FlashSalesController#index<\/code> action and we <em>don&rsquo;t<\/em> log a message, <code>flash_hash<\/code> is <code>nil<\/code>. That&rsquo;s because we never call <code>flash<\/code>, which would set the header that <code>flash_hash<\/code> reads from. <code>flash<\/code> never gets called, so the header never gets set. We never enter the conditional block in that scenario.<\/p><p>However, when we do log the message, the header is there, and <code>flash_hash<\/code> is present. That causes the contents of <code>flash_hash<\/code> to be converted to a session value and stored back in the session.<\/p><p>Converting a <code>FlashHash<\/code> instance to its <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L143\">session value<\/a> does the following:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">to_session_value<\/span><\/span><\/span><span style=\"display:flex;\"><span> flashes_to_keep <span style=\"color:#f92672\">=<\/span> @flashes<span style=\"color:#f92672\">.<\/span>except(<span style=\"color:#f92672\">*<\/span>@discard)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> <span style=\"color:#66d9ef\">nil<\/span> <span style=\"color:#66d9ef\">if<\/span> flashes_to_keep<span style=\"color:#f92672\">.<\/span>empty?<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">&#34;discard&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[]<\/span>, <span style=\"color:#e6db74\">&#34;flashes&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> flashes_to_keep }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we make a flash <em>from<\/em> the session, we send <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L135\">all the keys<\/a> as <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L150\">discard values<\/a>. As a result, when we&rsquo;re converting back <em>to<\/em> the session, <code>flashes_to_keep<\/code> is empty. This returns <code>nil<\/code>. And that&rsquo;s what we set <code>session[&quot;flash&quot;]<\/code> to.<\/p><p>The second conditional in <code>commit_flash<\/code> is:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">if<\/span> session<span style=\"color:#f92672\">.<\/span>loaded? <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">.<\/span>key?(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>) <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">].<\/span>nil?<\/span><\/span><span style=\"display:flex;\"><span> session<span style=\"color:#f92672\">.<\/span>delete(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we log the message, we load the session by calling <code>flash[:sale]<\/code>. That&rsquo;s because we&rsquo;re reading from the session to pass a value to <code>FlashHash.from_session_value<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">flash<\/span><\/span><\/span><span style=\"display:flex;\"><span> flash <span style=\"color:#f92672\">=<\/span> flash_hash<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> flash <span style=\"color:#66d9ef\">if<\/span> flash<\/span><\/span><span style=\"display:flex;\"><span> self<span style=\"color:#f92672\">.<\/span>flash <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Flash<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">FlashHash<\/span><span style=\"color:#f92672\">.<\/span>from_session_value(session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Accessing the session <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/request\/session.rb#L115\">loads the session for reading<\/a>, which through the <code>load!<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/request\/session.rb#L256\">method<\/a> sets the <code>@loaded<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/request\/session.rb#L280\">instance variable<\/a> that is used to <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/request\/session.rb#L236\">determine if a session is loaded<\/a>.<\/p><p>Because the session is loaded, it has a flash key, <strong>and<\/strong> the value is <code>nil<\/code> (because converting it to a session value discarded all the keys), then we delete flash from the session entirely.<\/p><p>If we never invoke <code>flash<\/code> before redirecting, we never load it from the session, never convert it back to a session value, and never delete it from the session. It remains available when processing the <code>ProductsController#index<\/code> action.<\/p><p>When we do call <code>flash<\/code> to write the log message, we <em>do<\/em> delete it from the session, so <code>ProductsController#index<\/code> doesn&rsquo;t have it.<\/p><h2 id=\"flash-forward\">Flash Forward <a href=\"#flash-forward\">\ud83d\udd17<\/a><\/h2><p>This is great that we understand <em>why<\/em> this is happening. But none of this actually fixes our problem. We want our tests to pass. We <em>don&rsquo;t<\/em> want the flash removed from the session. We want the <code>:sale<\/code> key to still exist.<\/p><p>Recall the conditions by which the flash is deleted from the session:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">if<\/span> session<span style=\"color:#f92672\">.<\/span>loaded? <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">.<\/span>key?(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>) <span style=\"color:#f92672\">&amp;&amp;<\/span> session<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;flash&#34;<\/span><span style=\"color:#f92672\">].<\/span>nil?<\/span><\/span><span style=\"display:flex;\"><span> session<span style=\"color:#f92672\">.<\/span>delete(<span style=\"color:#e6db74\">&#34;flash&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we log the message, we need to load the session to access the flash, so we can&rsquo;t get around that. However, if the value of <code>session[&quot;flash&quot;]<\/code> wasn&rsquo;t <code>nil<\/code>, that&rsquo;d be enough to preserve it.<\/p><p>The reason <code>session[&quot;flash&quot;]<\/code> is <code>nil<\/code> is because we didn&rsquo;t have any flashes to keep when converting the object to a session value.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">to_session_value<\/span><\/span><\/span><span style=\"display:flex;\"><span> flashes_to_keep <span style=\"color:#f92672\">=<\/span> @flashes<span style=\"color:#f92672\">.<\/span>except(<span style=\"color:#f92672\">*<\/span>@discard)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> <span style=\"color:#66d9ef\">nil<\/span> <span style=\"color:#66d9ef\">if<\/span> flashes_to_keep<span style=\"color:#f92672\">.<\/span>empty?<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">&#34;discard&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[]<\/span>, <span style=\"color:#e6db74\">&#34;flashes&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> flashes_to_keep }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We do have an option to explicitly keep the entire flash (or a particular key). It&rsquo;s the aptly-named <a href=\"https:\/\/github.com\/rails\/rails\/blob\/v8.0.1\/actionpack\/lib\/action_dispatch\/middleware\/flash.rb#L250\">keep<\/a> method. That removes keys from the <code>@discard<\/code> set, so they&rsquo;ll be retained.<\/p><p>Let&rsquo;s update our flash sale controller to use this.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FlashSalesController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">index<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#66d9ef\">FlashSale<\/span><span style=\"color:#f92672\">.<\/span>off?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#66d9ef\">AttemptLogger<\/span><span style=\"color:#f92672\">.<\/span>log?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>info <span style=\"color:#e6db74\">&#34;CTA &#39;<\/span><span style=\"color:#e6db74\">#{<\/span>flash<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:sale<\/span><span style=\"color:#f92672\">]<\/span><span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#39; used to access flash sale after it&#39;s over&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">+<\/span> flash<span style=\"color:#f92672\">.<\/span>keep(<span style=\"color:#e6db74\">:sale<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> redirect_to products_path <span style=\"color:#f92672\">and<\/span> <span style=\"color:#66d9ef\">return<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @products <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> donner_red_hss_starter_kit,<\/span><\/span><span style=\"display:flex;\"><span> martin_junior_acoustic,<\/span><\/span><span style=\"display:flex;\"><span> squier_affinity_strat_junior_hss,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>With that one call to <code>keep<\/code>, both of our tests pass. Whether we log the flash CTA or not, it&rsquo;s available for the <code>ProductsController#index<\/code> action to display.<\/p><h2 id=\"an-illuminating-flash\">An Illuminating Flash <a href=\"#an-illuminating-flash\">\ud83d\udd17<\/a><\/h2><p>Our goal was to preserve a flash message. Instead, it seems we learned more about the conditions by which we destroy the flash message. That allowed us to back up and explore options to fail that conditional.<\/p><p>The Rails source code <em>is<\/em> vast and dense, but it&rsquo;s also readily available. We discovered the reason behind the behavior we saw <strong>and<\/strong> a solution by being willing to explore it. Consider <code>bundle open<\/code>ing the next dependency you&rsquo;re confused or interested by. See what you can learn. It may take a while, or you may have your answer in a flash.<\/p>"},{"title":"Hugo Update","link":"https:\/\/kevinjmurphy.com\/posts\/2025-03-hugo-update\/","pubDate":"Mon, 10 Mar 2025 10:48:04 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/2025-03-hugo-update\/","description":"<p>I try to do the bare minimum of development work on this <em>actual<\/em> site. That way my time spent here is on actually blogging. That&rsquo;s why I chose <a href=\"https:\/\/gohugo.io\/\">hugo<\/a> over something like <a href=\"https:\/\/www.bridgetownrb.com\/\">Bridgetown<\/a>. By intentionally choosing a static site generator written in a language I&rsquo;m less familiar with, I&rsquo;m less likely to tinker.<\/p><p>Due to a tragedy with a git submodule this morning (that is&hellip;deleting it), I ran into an issue where my custom deploy script would no longer deploy. Attempts to reconfigure the submodule were not responsive. I&rsquo;m sure I could have figured it out, but&hellip;I didn&rsquo;t want to.<\/p><p>I found there is a more modern deployment story with Hugo now that is in their documentation - using <a href=\"https:\/\/gohugo.io\/host-and-deploy\/host-on-github-pages\/\">GitHub Actions<\/a>! I wanted to use that instead going forward.<\/p><p>That created a situation where the git submodule for the theme I use wasn&rsquo;t recognized in the GitHub Action. Updating the theme required updating Hugo itself and&hellip;here we are.<\/p><p>I&rsquo;ve noticed at least one visual regression, which is that my fancy social link icons aren&rsquo;t available on the top header (Update: they should be back now). If you&rsquo;re seeing this and missing them that much, I do keep track of <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">where to find me on the internet<\/a>.<\/p><p>If you find anything else that&rsquo;s not working or seems off, could you let me know?<\/p>"},{"title":"Frequently Played March 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-03\/","pubDate":"Mon, 10 Mar 2025 03:33:33 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-03\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"happy\">Happy <a href=\"#happy\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/vhumOLNSSJY?si=wYmX8FbBlYPKZ9DI\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Happy. Really happy?<\/p><p><a href=\"https:\/\/genius.com\/Nf-happy-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I&rsquo;ll be the first to admit that I&rsquo;m a lonely soul<br>And the last to admit I need a hand to hold<br>Losin&rsquo; hope, headed down a dangerous road<\/p><\/blockquote><h2 id=\"happy-now\">Happy Now <a href=\"#happy-now\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/B6mWRIwyFSk?si=4nR67NvWsMExu9Ln\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Most of the Tragic Kingdom album still hits pretty well.<\/p><p><a href=\"https:\/\/genius.com\/No-doubt-happy-now-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>The uncertainty you had of me<br>Brought clouded, shady company<br>The tenderness habitual<br>A seldom-fading ritual<\/p><\/blockquote>"},{"title":"Frequently Played Feb 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-02\/","pubDate":"Fri, 07 Feb 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-02\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"valentines-day\">Valentine&rsquo;s Day <a href=\"#valentines-day\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/HHdoqmz_lEw?si=jyi5GtA2lo6SJw-4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>&lsquo;Tis the season. I listen to the Tunnel of Love album throughout February everyyear.<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-valentines-day-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>So hold me close, honey, say you&rsquo;re forever mine<br>And tell me you&rsquo;ll be my lonely valentine<\/p><\/blockquote><h2 id=\"i-dont-mind-if-im-with-you\">I Don\u2019t Mind (If I\u2019m with You) <a href=\"#i-dont-mind-if-im-with-you\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/Xk2wsKBBPio?si=SnAMy1xLUwdyhnTn\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>It&rsquo;s interesting what people say they can endure for others. Even the wind.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-i-dont-mind-if-im-with-you-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Never had a good friend, no one like you<br>But I spent a lotta time on my own<br>And they set me on fire and I did a lot of burning<br>They told me I didn&rsquo;t know things I thought I knew for certain<br>But I, I don&rsquo;t mind<br>And now the wind&rsquo;s getting colder and the night&rsquo;s getting cruel<br>But I don&rsquo;t mind, I don&rsquo;t mind if I&rsquo;m with you<br>&hellip;<br>You&rsquo;ve been a light in the hallway in the dark of my room<br>Do you feel any better now?<\/p><\/blockquote>"},{"title":"What Is It (in Ruby 3.4)?","link":"https:\/\/kevinjmurphy.com\/posts\/what-is-it-in-ruby-34\/","pubDate":"Fri, 10 Jan 2025 15:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/what-is-it-in-ruby-34\/","description":"<p>This post may appear to be about new functionality in Ruby. However, it may instead be an attempt to write an article with the worst possible SEO.<\/p><p>Anyway, hit it.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/mTJ542VTMEE?si=MvYQFv9tqU3n1712\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h2 id=\"what-is-it\">What Is It? <a href=\"#what-is-it\">\ud83d\udd17<\/a><\/h2><p>As of Ruby 3.4, <code>it<\/code> is another way to refer to the first parameter in a block. This may be more obvious by way of an example.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;What is it?&#34;<\/span><span style=\"color:#f92672\">].<\/span>map { it <span style=\"color:#f92672\">+<\/span> <span style=\"color:#e6db74\">&#34; It&#39;s it&#34;<\/span> } <span style=\"color:#f92672\">*<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>The block we pass to <code>map<\/code> does not define a name for the parameter to represent the variable we&rsquo;re manipulating in each iteration. Instead, Ruby knows <code>it<\/code> refers to that variable.<\/p><p>Starting in Ruby 2.7, we had access to numbered parameters in a block with an underscore. We can write that same example using numbered parameters.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;What is it?&#34;<\/span><span style=\"color:#f92672\">].<\/span>map { _1 <span style=\"color:#f92672\">+<\/span> <span style=\"color:#e6db74\">&#34; It&#39;s it&#34;<\/span> } <span style=\"color:#f92672\">*<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Before Ruby 2.7, we would define the name of a variable in a block, which we can still do with more recent versions of Ruby.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;What is it?&#34;<\/span><span style=\"color:#f92672\">].<\/span>map { <span style=\"color:#f92672\">|<\/span>i<span style=\"color:#f92672\">|<\/span> i <span style=\"color:#f92672\">+<\/span> <span style=\"color:#e6db74\">&#34; It&#39;s it&#34;<\/span> } <span style=\"color:#f92672\">*<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;What is it? It&#39;s it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"handling-nested-calls\">Handling nested calls <a href=\"#handling-nested-calls\">\ud83d\udd17<\/a><\/h2><p>Should you have nested blocks that refer to <code>it<\/code>, Ruby will take <code>it<\/code> to refer to the parameter of the innermost block.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;You want it all, but you can&#39;t have it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;It&#39;s in your face, but you can&#39;t grab it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>map { it <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span> ? <span style=\"color:#f92672\">[<\/span>it, it, it<span style=\"color:#f92672\">].<\/span>map { it<span style=\"color:#f92672\">.<\/span>upcase }<span style=\"color:#f92672\">.<\/span>join(<span style=\"color:#e6db74\">&#34;, &#34;<\/span>) : it }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;You want it all, but you can&#39;t have it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;YEAH, YEAH, YEAH&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;It&#39;s in your face, but you can&#39;t grab it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;YEAH, YEAH, YEAH&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>The same is not possible with numbered parameters. An exception is raised due to the ambiguity of what <code>_1<\/code> refers to.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;You want it all, but you can&#39;t have it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;It&#39;s in your face, but you can&#39;t grab it&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>map { _1 <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span> ? <span style=\"color:#f92672\">[<\/span>_1, _1, _1<span style=\"color:#f92672\">].<\/span>map { _1<span style=\"color:#f92672\">.<\/span>upcase }<span style=\"color:#f92672\">.<\/span>join(<span style=\"color:#e6db74\">&#34;, &#34;<\/span>) : _1 }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>syntax error found (<span style=\"color:#66d9ef\">SyntaxError<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">5<\/span> <span style=\"color:#f92672\">|<\/span> <span style=\"color:#e6db74\">&#34;Yeah&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">6<\/span> <span style=\"color:#f92672\">|<\/span> <span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#ae81ff\">7<\/span> <span style=\"color:#f92672\">|<\/span> <span style=\"color:#f92672\">...<\/span> _1<span style=\"color:#f92672\">.<\/span>upcase }<span style=\"color:#f92672\">.<\/span>join(<span style=\"color:#e6db74\">&#34;, &#34;<\/span>) : _1 }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">|<\/span> <span style=\"color:#f92672\">^~<\/span> numbered parameter is already used <span style=\"color:#66d9ef\">in<\/span> outer block<\/span><\/span><\/code><\/pre><\/div><h2 id=\"method-names-in-scope\">Method names in scope <a href=\"#method-names-in-scope\">\ud83d\udd17<\/a><\/h2><p>Ruby also will infer that <code>it<\/code> refers to the block parameter should there be a method called <code>it<\/code> in scope.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">it<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;This isn&#39;t it&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Can you feel it, see it, hear it today?&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;If you can&#39;t, then it doesn&#39;t matter anyway&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>map { it<span style=\"color:#f92672\">.<\/span>gsub(<span style=\"color:#e6db74\">&#34;it&#34;<\/span>, <span style=\"color:#e6db74\">&#34;IT&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Can you feel IT, see IT, hear IT today?&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;If you can&#39;t, then IT doesn&#39;t matter anyway&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"variable-names-in-scope\">Variable names in scope <a href=\"#variable-names-in-scope\">\ud83d\udd17<\/a><\/h2><p>However, if there is a <strong>variable<\/strong> defined as <code>it<\/code>, that will take precedence over the block parameter in the block.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;This isn&#39;t it&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;You will never understand it, &#39;cause it happens too fast&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;And it feels so good, it&#39;s like walking on glass&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>map { it<span style=\"color:#f92672\">.<\/span>gsub(<span style=\"color:#e6db74\">&#34;it&#34;<\/span>, <span style=\"color:#e6db74\">&#34;IT&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;This isn&#39;t IT&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;This isn&#39;t IT&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"youve-got-to-share-it-so-you-dare-it\">You&rsquo;ve got to share it, so you dare it <a href=\"#youve-got-to-share-it-so-you-dare-it\">\ud83d\udd17<\/a><\/h2><p>If you want to learn more about this new feature, check out the <a href=\"https:\/\/bugs.ruby-lang.org\/issues\/18980\">proposal<\/a> and the <a href=\"https:\/\/docs.ruby-lang.org\/en\/3.4\/Proc.html#class-Proc-label-it\">documentation<\/a>. Read the <a href=\"https:\/\/rubyreferences.github.io\/rubychanges\/3.4.html\">Ruby 3.4 changelog<\/a> to discover all the fun available in Ruby 3.4.<\/p><blockquote><p>You can touch it, smell it, taste it, so sweet<br>But it makes no difference &lsquo;cause it knocks you off your feet<\/p><\/blockquote><p>That&rsquo;s it.<\/p>"},{"title":"Frequently Played Jan 2025","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-01\/","pubDate":"Sun, 05 Jan 2025 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2025-01\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"zero\">Zero <a href=\"#zero\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/IIEJmdYGpW8?si=wfRNHqSDR5_R2gE4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>These riffs are super fun to play. My maybe toxic 90&rsquo;s music take is that Jimmy Chamberlin is the best drummer of the bunch. Sorry Danny Carey, Josh Freese, and others.<\/p><p><a href=\"https:\/\/genius.com\/The-smashing-pumpkins-zero-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I never let on<br>That I was on a sinkin&rsquo; ship<br>I never let on that I was down<br>You blame yourself<br>For what you can&rsquo;t ignore<br>You blame yourself for wanting more<\/p><\/blockquote><h2 id=\"were-in-this-together\">We&rsquo;re In This Together <a href=\"#were-in-this-together\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/KiXTDgvyanQ?si=lDQIQgLBC1XzVOIW\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Rather than the <a href=\"https:\/\/youtu.be\/P9BfvPjsXXw?si=9-RpGTapuM2PIJiV\">original<\/a>, I&rsquo;ve specifically been listening to the version from Allie Goertz&rsquo;s <a href=\"https:\/\/alliegoertz.bandcamp.com\/album\/peeled-back\">Peeled Back<\/a> album of Nine Inch Nails covers. The album strikes me as full of earnest representations of the songs, rather than pandering or playing for a cheeky response.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-were-in-this-together-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>When all our hope is gone<br>We have to hold on<br>All that we were is gone<br>But we can hold on<\/p><\/blockquote>"},{"title":"2024 Year-End Review","link":"https:\/\/kevinjmurphy.com\/posts\/2024-review\/","pubDate":"Mon, 16 Dec 2024 14:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/2024-review\/","description":"<p>This isn&rsquo;t a &ldquo;real&rdquo; post. This is a summary of all the things that made up my year in 2024 (almost all in a professional context). Thanks to all who were a part of it.<\/p><h2 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h2><p>I published 7 articles about Ruby or software development in general this year. The first was about possible <a href=\"https:\/\/kevinjmurphy.com\/posts\/tracks-not-at-railsconf-2024\/\">RailsConf tracks<\/a>. My last post of the year covered what I saw at <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2024-recap\/\">RubyConf<\/a>.<\/p><p>In between conference posts, I wrote about <a href=\"https:\/\/kevinjmurphy.com\/posts\/making-a-sidekiq-batch-recipe\/\">Sidekiq batches<\/a>, accessing <a href=\"https:\/\/kevinjmurphy.com\/posts\/access-request-headers-in-rails-controller\/\">request headers<\/a> in Rails, and <a href=\"https:\/\/kevinjmurphy.com\/posts\/rspec-stubs-object-in-memory\/\">RSpec stubbing<\/a>.<\/p><p>I continue sending my posts to <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newsletter subscribers<\/a>. You can still subscribe via <a href=\"https:\/\/kevinjmurphy.com\/index.xml\">RSS<\/a> or try to keep up wherever I post on <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">social media<\/a>.<\/p><p>My most-read posts are those that end up in <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">other newsletters<\/a>. This year, my writing appeared in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> 8 times, <a href=\"https:\/\/newsletter.shortruby.com\/\">Short Ruby Newsletter<\/a> 6 times, <a href=\"https:\/\/a.tldrnewsletter.com\/web-version?ep=1&amp;lc=370b268c-04a2-11ef-ae94-154a86672d19&amp;p=d99d0dce-174a-11ef-8688-c9b4f033c41d&amp;pt=campaign&amp;t=1716289633&amp;s=f8d64a684b167de86dcb877ce42b9aa5b55fba7f1cbe8c8d40a248c4b598149c\">TL;DR Web Dev<\/a> once, and <a href=\"https:\/\/rubycentral.org\/news\/may-2024-newsletter\/\">Ruby Central&rsquo;s monthly newsletter<\/a> once.<\/p><p>I cut down my publishing schedule again. Intentionally. I started the year saying I was going to <a href=\"https:\/\/kevinjmurphy.com\/posts\/slowing-down\/\">slow down<\/a> and then immediately joined the <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/\">RailsConf Programming Committee<\/a>. Whoops. But also worth it.<\/p><p>I&rsquo;ll continue to have a lighter content calendar indefinitely. It may quickly turn into a &ldquo;post whenever I want&rdquo; situation, rather than the current every other month.<\/p><h2 id=\"speaking\">Speaking <a href=\"#speaking\">\ud83d\udd17<\/a><\/h2><p>I did submit to one CFP this year, but was not accepted. Speaking was not a priority for me this year.<\/p><p>I helped conference speakers more this year. I was a coach for the RubyCentral CFP coaching sessions for RailsConf. I was a speaker mentor at RailsConf. I helped friends prepare their proposals and talks when asked.<\/p><p>As mentioned in the writing section, I was also on the <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/\">RailsConf Programming Committee<\/a>. That was a new experience for me. It was an honor to be asked and to help bring the conference together. I hope everyone that attended enjoyed it.<\/p><p>On the podcasting front, I was a guest on one episode of <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2024\/\">Ruby for All<\/a> to speak about RailsConf.<\/p><p>If you have an event you&rsquo;d like me to speak at, or a podcast you&rsquo;d like me to guest on, let me know.<\/p><h2 id=\"employer\">Employer <a href=\"#employer\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve been at <a href=\"https:\/\/www.pubmark.com\/\">Pubmark<\/a> all year. I enjoy the work that I do, and the people I do it with. There have been some shifts and changes, but I&rsquo;ve been trying to stay in my comfort zone. It&rsquo;s been helpful for my personal life.<\/p><h2 id=\"new-to-me-in-2024\">New (to me) in 2024 <a href=\"#new-to-me-in-2024\">\ud83d\udd17<\/a><\/h2><p>Here&rsquo;s where I take a break to step out of work and talk about things (mostly music) I got into this year.<\/p><h3 id=\"nf---the-search\">NF - The Search <a href=\"#nf---the-search\">\ud83d\udd17<\/a><\/h3><p>I was introduced to this artist this year and I&rsquo;m thankful for it.<\/p><blockquote><p>I&rsquo;m lookin&rsquo; for the map to hope, you seen it?<\/p><\/blockquote><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/fnlJw9H0xAM?si=oQpbbOwAGTTMWWok\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h3 id=\"front-desk\">Front Desk <a href=\"#front-desk\">\ud83d\udd17<\/a><\/h3><p>A quick <a href=\"https:\/\/frontdeskthebook.com\/\">book<\/a>, but the most enjoyable reading experience I had this year.<\/p><h3 id=\"metallica-live\">Metallica (Live) <a href=\"#metallica-live\">\ud83d\udd17<\/a><\/h3><p>I&rsquo;ve been PRETTY AWARE of them, but somehow I&rsquo;ve avoided seeing Metallica live. This changed over the summer when I saw them twice. This video is from one of the shows I attended.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/mFp-4MQEeyQ?si=mqt23TEHng_tztwO\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h3 id=\"magic-and-illusion\">Magic and Illusion <a href=\"#magic-and-illusion\">\ud83d\udd17<\/a><\/h3><p>I&rsquo;m not learning it myself, but I witnessed some \u2728<a href=\"https:\/\/www.nowherebookshop.com\/book\/9781250119889\">magic<\/a>\u2728 this year.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/5_KcQt0z-eE?si=i_GUBGL_QvYATGmB\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>And I&rsquo;m left wondering what&rsquo;s real and what was an illusion.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/mXZgJ2XJH0E?si=3D4T6VQ_UEY7utLO\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h2 id=\"thank-you\">Thank You <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>Thanks for reading, watching, or listening. Take care of yourself. Help take care of others to the extent you&rsquo;re able to. Reach out to others when you need it.<\/p>"},{"title":"Frequently Played Dec 2024","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-12\/","pubDate":"Fri, 13 Dec 2024 06:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-12\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"the-river\">The River <a href=\"#the-river\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/Cc1fKQcRALk?si=LDnT73A5zXIUsoVL\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Bruce is my favorite storyteller. He speaks to me. Helps me understand stories beyond my own, while also making sense of things in my life.<\/p><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-the-river-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Now those memories come back to haunt me<br>They haunt me like a curse<br>Is a dream a lie if it don&rsquo;t come true?<br>Or is it something worse?<\/p><\/blockquote><h2 id=\"get-hurt\">Get Hurt <a href=\"#get-hurt\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/1MuHywpeuQY?si=nmx-M6pfNo3Jp-K9\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This blog isn&rsquo;t the first time The Gaslight Anthem and Bruce Springsteen <a href=\"https:\/\/youtu.be\/y1atupgwaew?si=imMgLS60Q17Qatje\">got together<\/a>. Not the latest <a href=\"https:\/\/youtu.be\/CjhVOCQqUPs?si=YnEtVxzRE8X3udsC\">official time<\/a> either.<\/p><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-get-hurt-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Have you come to take her away from me?<br>From me, from me<br>Might as well do your worst to me<br>And maybe you needed a change or maybe I was in the way<br>Maybe some things they stay and some things go away<\/p><\/blockquote>"},{"title":"RubyConf 2024 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2024-recap\/","pubDate":"Sat, 16 Nov 2024 11:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2024-recap\/","description":"<h2 id=\"rubyconf-2024\">RubyConf 2024 <a href=\"#rubyconf-2024\">\ud83d\udd17<\/a><\/h2><p>RubyConf recently wrapped up in Chicago, Illinois. This post is meant to highlight the great work from all involved. I documented my updates from &ldquo;on the ground&rdquo; <a href=\"https:\/\/kevinjmurphy.com\/posts\/at_rubyconf2024\/\">elsewhere<\/a>. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you once they are available. Unfortunately, I couldn&rsquo;t be everywhere, so this covers what I saw. I certainly did not come anywhere close to doing all the things I planned.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><h3 id=\"opening-keynote\">Opening Keynote <a href=\"#opening-keynote\">\ud83d\udd17<\/a><\/h3><p>Yukihiro &ldquo;Matz&rdquo; Matsumoto started the conference after the opening remarks. He reviewed some key features introduced in Ruby throughout the years. Ruby was originally created for a developer with the following interests:<\/p><ul><li>Loves programming<\/li><li>Loves freedom<\/li><li>Wants to be productive<\/li><\/ul><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/matz.jpg\" alt=\"Matz at RubyConf 2024\"><\/figure><h3 id=\"names-from-a-hat\">Names from a hat <a href=\"#names-from-a-hat\">\ud83d\udd17<\/a><\/h3><p>Murray Steele sampled different approaches to retrieving subsets of data. With the goal of fun and whimsy, you can be open to safely explore new tools or paradigms.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/murray.jpg\" alt=\"Murray Steele at RubyConf 2024\"><\/figure><h3 id=\"compose-software-like-nature-would\">Compose Software Like Nature Would <a href=\"#compose-software-like-nature-would\">\ud83d\udd17<\/a><\/h3><p>Ahmed Omran used metaphors from the world around us to inform how we can design our software systems.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/ahmed.jpg\" alt=\"Ahmed Omran at RubyConf 2024\"><\/figure><h3 id=\"coordinate-ruby-teams\">Coordinate Ruby Teams <a href=\"#coordinate-ruby-teams\">\ud83d\udd17<\/a><\/h3><p>Noel Rappin and Brandt Sheets from the I Heart Ruby team at Chime shared tooling they&rsquo;ve built to share standards and progress across teams.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/noel.jpg\" alt=\"Noel Rappin at RubyConf 2024\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/brandt_sheets.jpg\" alt=\"Brandt Sheets at RubyConf 2024\"><\/figure><\/p><h3 id=\"the-state-of-rubygems\">The State of RubyGems <a href=\"#the-state-of-rubygems\">\ud83d\udd17<\/a><\/h3><p>Samuel Giddins, Martin Emde, and Marty Haught addressed what improvements have been made to RubyGems and shared interests for the future.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/samuel_giddens.jpg\" alt=\"Samuel Giddins at RubyConf 2024\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/martin_emde.jpg\" alt=\"Martin Emde at RubyConf 2024\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/marty_haught.jpg\" alt=\"Marty Haught at RubyConf 2024\"><\/figure><\/p><h3 id=\"chaos-engineering-on-the-death-star\">Chaos Engineering on the Death Star <a href=\"#chaos-engineering-on-the-death-star\">\ud83d\udd17<\/a><\/h3><p>Sara Jackson calmly walked us through a chaotic environment. We were introduced to different tools and approaches to consider to harden and better understand our production systems at the application layer.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/sara.jpg\" alt=\"Sara Jackson at RubyConf 2024\"><\/figure><h3 id=\"fast-forward-speeding-up-delegate-methods\">Fast Forward: Speeding up Delegate Methods <a href=\"#fast-forward-speeding-up-delegate-methods\">\ud83d\udd17<\/a><\/h3><p>Aaron Patterson skipped the jokes for more time to talk performance tuning. This time the focus was on delegating method arguments. The talk is a good demonstration or reminder of stack pointers, specifically presented in how it&rsquo;s relevant in this area.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/aaron.jpg\" alt=\"Aaron Patterson at RubyConf 2024\"><\/figure><h3 id=\"closing-keynote\">Closing Keynote <a href=\"#closing-keynote\">\ud83d\udd17<\/a><\/h3><p>Nickolas Means presented a disastrous situation complicated by the reliance on automation and the level of abstraction that introduces.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/nickolas.jpg\" alt=\"Nickolas Means at RubyConf 2024\"><\/figure><h2 id=\"day-2---hack-day-and-workshops\">Day 2 - Hack Day and Workshops <a href=\"#day-2---hack-day-and-workshops\">\ud83d\udd17<\/a><\/h2><h3 id=\"who-wants-to-be-a-ruby-engineer\">Who Wants to be a Ruby Engineer? <a href=\"#who-wants-to-be-a-ruby-engineer\">\ud83d\udd17<\/a><\/h3><p>Drew Bragg brought his game show to the RubyConf main stage. This picture is blurry because it&rsquo;s impossible to get a clear one. Drew is never still on stage long enough.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/drew_stage.jpg\" alt=\"Drew Bragg at RubyConf 2024\"><\/figure><h3 id=\"no-static-types-no-problem\">No Static Types? No Problem! <a href=\"#no-static-types-no-problem\">\ud83d\udd17<\/a><\/h3><p>Noel Rappin delivered an even-handed summary of different type solutions available in Ruby. And included how we can achieve some of the benefits of types, or address some of the concerns that leads people to call for types, even in the absence of having them available to us.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/noel_rappin_workshop.jpg\" alt=\"Noel Rappin at RubyConf 2024\"><\/figure><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><h3 id=\"opening-keynote-1\">Opening Keynote <a href=\"#opening-keynote-1\">\ud83d\udd17<\/a><\/h3><p>Brandon Weaver brought Fantasyland to RubyConf. We went down the rabbit hole for story hour.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/brandon.jpg\" alt=\"Brandon Weaver at RubyConf 2024\"><\/figure><h3 id=\"mvc-ruby-in--5k-the-wonder-of-camping\">MVC Ruby in &lt; 5k. The wonder of Camping <a href=\"#mvc-ruby-in--5k-the-wonder-of-camping\">\ud83d\udd17<\/a><\/h3><p>Karl Weber shared how the Camping web framework works. The knowledge and awareness of other frameworks, even if you aren&rsquo;t going to use it, is important. It shows a vibrant and growing ecosystem. It provides insights and options to inform the code you write, even if you&rsquo;re not working within the framework.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/karl.jpg\" alt=\"Karl Weber at RubyConf 2024\"><\/figure><h3 id=\"10-years-of-roda\">10 Years of Roda <a href=\"#10-years-of-roda\">\ud83d\udd17<\/a><\/h3><p>Jeremy Evans followed to explore how Roda has changed from its first RubyConf presentation a decade ago.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/jeremy_evans.jpg\" alt=\"Jeremy Evans at RubyConf 2024\"><\/figure><h3 id=\"closing-keynote-1\">Closing Keynote <a href=\"#closing-keynote-1\">\ud83d\udd17<\/a><\/h3><p>Nadia Odunayo walked us through another mystery in her private detective series. This ended with a dramatic twist that&rsquo;s not to be missed.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/nadia_keynote.jpg\" alt=\"Nadia Odunayo at RubyConf 2024\"><\/figure><h2 id=\"appreciation\">Appreciation <a href=\"#appreciation\">\ud83d\udd17<\/a><\/h2><p>Thank you to my employer, <a href=\"https:\/\/www.pubmark.com\/\">Pubmark<\/a>, for supporting my attendance this year.<\/p><p>Thank you <a href=\"https:\/\/rubycentral.org\">Ruby Central<\/a> for your work to organize these events and supporting the Ruby community all year round.<\/p><p>Thank you to the Program Chairs: Kinsey Durham Grace and Jim Remsik.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/kinsey_jim.jpg\" alt=\"Kinsey Durham Grace and Jim Remsik at RubyConf 2024\"><\/figure><p>Thank you to the Program Committee, Scholarship Committee, volunteers, and venue staff for bringing this conference to life.<\/p><p>Thank you to Allison McMillan and Adarsh Pandit for your work on the Ruby Central board. I appreciate all your efforts throughout the years.<figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/allie.jpg\" alt=\"Adarsh Pandit at RubyConf 2024\"><\/figure><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/speakers\/adarsh.jpg\" alt=\"Adarsh Pandit at RubyConf 2024\"><\/figure><\/p><p>Thank you sponsors for supporting the Ruby community and making it possible for us to come together.<\/p><p>Thanks to all the #RubyFriends, old and new, that I met in Chicago.<\/p><p>Thank you for reading to the end. Maybe we met at RubyConf 2024. Maybe we didn&rsquo;t. Maybe we were supposed to, but never saw each other. I hope to see you in 2025.<\/p>"},{"title":"At RubyConf 2024","link":"https:\/\/kevinjmurphy.com\/posts\/at_rubyconf2024\/","pubDate":"Tue, 12 Nov 2024 05:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/at_rubyconf2024\/","description":"<h2 id=\"rubyconf-2024\">RubyConf 2024 <a href=\"#rubyconf-2024\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m starting this post from Terminal B in Logan Airport (Boston). Waiting for my plane, I figured I might put together a post to document a quick and loose exploration of what I see while I&rsquo;m in Chicago. Check out my <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2024-recap\/\">recap post<\/a> for more details about the sessions I visited.<\/p><h2 id=\"travel\">Travel <a href=\"#travel\">\ud83d\udd17<\/a><\/h2><p>I had a surprisingly uneventful airport experience. Got there bright and early. Left and arrived without issue.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/airplane_window.jpg\" alt=\"Obligatory photo out an airplane window\"><\/figure><p>For anyone landing at Midway, look for the &ldquo;Ground Transportation&rdquo; signs, and eventually &ldquo;CTA&rdquo;. The Orange Line Loop to Roosevelt will get you close to the conference hotel.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/orange_line.jpg\" alt=\"Chicago Transit Authority Orange Line Map\"><\/figure><h2 id=\"hotel\">Hotel <a href=\"#hotel\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m staying at the conference hotel. I&rsquo;ve got myself a fancy city view. It&rsquo;s only MOSTLY a view of HVAC systems from the next building (not pictured).<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/city_line.jpg\" alt=\"The Chicago city skyline from my hotel window\"><\/figure><p>After checking in, I immediately saw Matz in the lobby with a group. I didn&rsquo;t say hi, but it confirmed I&rsquo;m in the right place.<\/p><h2 id=\"a-walk-in-the-park\">A Walk in the Park <a href=\"#a-walk-in-the-park\">\ud83d\udd17<\/a><\/h2><p>The hotel is right by a series of parks, so I wandered around for a while on my own.<\/p><p>I stretched my legs.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/legs.jpg\" alt=\"A series of legs made of what looks to be bronze castings\"><\/figure><p>I marveled at the existence of dedicated bike infrastructure.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/bike_lane.jpg\" alt=\"Dedicated bike lanes\"><\/figure><p>Unlike at RailsConf in Detroit this year, I could not see Canada from across the water.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/lake.jpg\" alt=\"Lake Michigan\"><\/figure><p>I still managed to make a splash.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/splash.jpg\" alt=\"The waves from the lake splashing along the walking path\"><\/figure><p>It was a bit unseasonable to stay at the beach, so I just walked by.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/beach.jpg\" alt=\"A beach on the shore of Lake Michigan\"><\/figure><p>I unexpectedly ran across some fairies.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/fairy.jpg\" alt=\"A statue of what I think are fairies in a garden. I must admit, I&#39;ve seen more beautiful fairies.\"><\/figure><p>Some of the walk was for the birds.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/bird.jpg\" alt=\"A sign that says McCormick Bird Sanctuary\"><\/figure><p>I scoped out a venue for my next speaking engagement.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/soldier_field.jpg\" alt=\"An entrance to Soldier Field\"><\/figure><p>Towards the end I realized I walked around the entire globe.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/globe.jpg\" alt=\"A statue a globe\"><\/figure><p>After I returned, it was time for early registration. I met some #RubyFriends and passed the afternoon with them.<\/p><p>It seems like Kitty O&rsquo;Sheas is the conference bar of choice after hours. At least, it was tonight.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><p>I went to Yolk for breakfast and had french toast. It was good! It&rsquo;s a few blocks straight down the street from the conference hotel.<\/p><p>I had already pre-registered, but I headed down to the ballroom area after eating to see how things were coming together.<\/p><p>I have this weird strategy at conferences: I go to the talks. Today I was in sessions with Matz, Murray Steele, Ahmed Omran, Noel Rappin, Brandt Sheets, Samuel Giddens, Martin Emde, Marty Haught, Sara Jackson, Aaron Patterson, and Nickolas Means.<\/p><p>Everyone did a great job. I&rsquo;ll have my recap post later. On to the random pictures of the day&hellip;<\/p><p>At lunch they served coconut creme brulee in these tiny mugs. It was tasty and unexpected.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/creme_brulee.jpg\" alt=\"A serving of creme brulee and chocolate cake\"><\/figure><p>Do you think the hotel has these rubies in this display case all the time, or just for us?<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/ruby_statues.jpg\" alt=\"A display case with jewelry and trinkets that displays large rubies\"><\/figure><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><p>Breakfast with the Bragg family started the day&hellip;not to brag. And then it was off to the Keynote for the morning.<\/p><p>Hack day is always kind of a weird day for me. I&rsquo;m glad that other people like it. I wandered for the rest of the morning, moving from conversation to conversation and actively not hacking.<\/p><p>I spent the afternoon in Noel Rappin&rsquo;s workshop, which was well-executed (of course).<\/p><p>After a quiet (appreciated) dinner, I spent some time at a thoughtbot event and talked to some #RubyFriends at game night before settling in for the evening.<\/p><p>No fun pictures today. Which doesn&rsquo;t mean no fun was had.<\/p><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><p>Last day, and an early start. I managed to fit in time for breakfast before Brandon&rsquo;s Keynote. I then watched talks from Karl Weber and Jeremy Evans before Nadia&rsquo;s Keynote.<\/p><p>I stopped by the reception to say some goodbyes. Then headed out with a small group to try an escape room. I can&rsquo;t say I was much help, but we did succeed. You&rsquo;re welcome everyone.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2024\/agent_bob.jpg\" alt=\"A drawing of a person labeled Agent Bob with the saying, &#39;for saving the world, all I ask for is free parking for life.&#39;\"><\/figure><p>I met up with a group for dinner and then called it a night.<\/p><p>I&rsquo;m ending RubyConf tired but thankful. I wish I saw more people. And had more time with the people I did see. But I&rsquo;m so thankful for the work RubyCentral does year round to support our community. And the investment they make in bringing us together at events like this. Thank you #RubyFriends.<\/p><p>Check out my <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2024-recap\/\">recap post<\/a> for more details about the sessions I visited.<\/p>"},{"title":"Frequently Played Nov 2024","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-11\/","pubDate":"Wed, 06 Nov 2024 20:00:50 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-11\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"great-expectations\">Great Expectations <a href=\"#great-expectations\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/3xYVyi6n7cA?si=SqaG5k40RcbAwJ0U\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>I have to admit, I&rsquo;ve never read the book.<\/p><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-great-expectations-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>And I learned about the blues from this kitten that I knew<br>Her hair was raven and her heart was like a tomb<br>My heart is like a wound<br>And I saw taillights last night in a dream about my first wife<br>Everybody leaves and I&rsquo;d expect as much from you<br>I saw taillights last night in a dream about my old life<br>Everybody leaves, Mary why, why wouldn&rsquo;t you?<\/p><\/blockquote><h2 id=\"isnt-everyone\">Isn&rsquo;t Everyone <a href=\"#isnt-everyone\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/6W2E6jNXzBE?si=6PwZFbUlHuurPmJE\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Good luck out there everyone.<\/p><p><a href=\"https:\/\/genius.com\/22998361\">Full Lyrics<\/a><\/p><blockquote><p>Nothing matters, we&rsquo;re all to blame<br>We&rsquo;ve all surrendered to those we serve<br>We get the world we all deserve<\/p><\/blockquote>"},{"title":"Happy Halloween 2024","link":"https:\/\/kevinjmurphy.com\/posts\/happy-halloween-2024\/","pubDate":"Thu, 31 Oct 2024 08:29:50 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/happy-halloween-2024\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>Maybe you&rsquo;re wearing a witch hat today, like my daughter will be. Maybe you&rsquo;d befine treating it like just another day, like I am. Either way, enjoy the day.<\/p><h2 id=\"halloween\">Halloween <a href=\"#halloween\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/si8Tpp5En-I?si=KlTvqU5y4Fi45EyJ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p><a href=\"https:\/\/genius.com\/The-gaslight-anthem-halloween-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>And who are you supposed to be?<br>You look like heaven tonight<br>Me, I&rsquo;m a tomb, a corpse in a suit<br>Trying to look a little alive<br>Are you alright?<br>&lsquo;Cause I worry sometimes<br>Are you dressed up to take my life?<\/p><\/blockquote><h2 id=\"a-night-with-the-jersey-devil\">A Night with the Jersey Devil <a href=\"#a-night-with-the-jersey-devil\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/l6leNRGhOqA?si=4uTk7wjP7KUS7Srk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p><a href=\"https:\/\/genius.com\/Bruce-springsteen-a-night-with-the-jersey-devil-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Set my flesh to burning, whilst I sleep<br>I burn, burn, burn, till my soul burn black<br>Black rains fall, I come back, I come back<\/p><\/blockquote><h2 id=\"monster-mash\">Monster Mash <a href=\"#monster-mash\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2B-9GgbK9dk?si=pb3eyGYSQYHpIp5G\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Certainly not their strongest effort.<\/p><p>Coincidentally, the E Street Band starts the Canadian swing of their tour tonight. I&rsquo;d definitely run away to Canada and catch a show if I could.<\/p><blockquote><p>This is terrible!<\/p><\/blockquote>"},{"title":"Two Drawer End Tables Construction","link":"https:\/\/kevinjmurphy.com\/posts\/two-drawer-end-tables\/","pubDate":"Sun, 13 Oct 2024 11:40:50 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/two-drawer-end-tables\/","description":"<h2 id=\"building-in-3d\">Building in 3D <a href=\"#building-in-3d\">\ud83d\udd17<\/a><\/h2><p>I make a lot of things. Most commonly code. I <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">blog<\/a> (maybe you&rsquo;re aware, given you&rsquo;re here). I build <a href=\"https:\/\/kevinjmurphy.com\/speaking\/\">conference talks<\/a>. Sometimes manifestos. Most of these live on the computer. Sometimes I need to step away and not look at a screen.<\/p><p>On occasion, I&rsquo;ll build some piece of furniture we need in the house. Or some small accessory out of wood that I&rsquo;ll use on my desk. This is one such time, and I decided to document it.<\/p><p>Similar to my recent posts on <a href=\"https:\/\/kevinjmurphy.com\/tags\/music\/\">music<\/a> I&rsquo;m listening to, this isn&rsquo;t directly related to software development. If you want to bail because of that, no judgement.<\/p><h2 id=\"the-problem\">The Problem <a href=\"#the-problem\">\ud83d\udd17<\/a><\/h2><p>There wasn&rsquo;t really so much a problem. I recently built an <a href=\"https:\/\/kevinjmurphy.com\/posts\/office-end-table-construction\/\">end table<\/a> for my office. I had extra drawer slides. I had some leftover wood. And I wanted to keep building. So I decided to build some similar tables. These would each have two drawers. That will give me more practice with drawers, given my past issues successfully making working drawers.<\/p><h2 id=\"the-design\">The Design <a href=\"#the-design\">\ud83d\udd17<\/a><\/h2><p>This design borrows heavily from the other table. It&rsquo;s meant to be cohesive. The dimensions are a bit different. These tables are smaller. And they won&rsquo;t have a shelf on the bottom. Instead, there&rsquo;s the second drawer.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pair_tables\/design.jpg\" alt=\"End table design on paper\"><\/figure><h2 id=\"the-materials\">The Materials <a href=\"#the-materials\">\ud83d\udd17<\/a><\/h2><p>This is all the same as the past build.<\/p><p>This requires 1&quot;x6&quot; and 2&quot;x2&quot; lumber. For the 1x6s, I&rsquo;m using poplar. It&rsquo;s (relatively) cheap and (I think) a step up from soft pine. Some people don&rsquo;t like the look of poplar. I totally get that, but the majority of this will be painted anyway.<\/p><p>For the 2x2s, I&rsquo;m not actually going to purchase 2x2s. Instead, I&rsquo;ll grab the straightest 2x4 wall studs I can possibly find. I dig through the giant pallet of 2x4s at the big box store. It&rsquo;s more economical for me to do that.<\/p><p>The drawer box will use 3\/4&quot; plywood for the sides and 1\/4&quot; plywood for the bottom.<\/p><p>I&rsquo;m using pocket screws again. It&rsquo;s quick. It&rsquo;s effective. I don&rsquo;t care if people think it&rsquo;s beneath them. It&rsquo;s a tool, and one that works well in this application.<\/p><h2 id=\"construction\">Construction <a href=\"#construction\">\ud83d\udd17<\/a><\/h2><p>This won&rsquo;t go in-depth into the steps I took to build these. That&rsquo;s for a few reasons. First, it&rsquo;d be really repetitive. If you&rsquo;re interested, check out the <a href=\"https:\/\/kevinjmurphy.com\/posts\/office-end-table-construction\/\">last build<\/a>. The dimensions are different, but I followed the same steps. Also, I didn&rsquo;t take in-progress pictures. I felt they&rsquo;d be redundant, and I was focused on building.<\/p><p>One thing I did differently was when constructing the tabletops, I didn&rsquo;t use pocket screws. I only used glue. This required more attention, and some additional clamping and helper wood, to try to keep everything level.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pair_tables\/glue_up.jpg\" alt=\"A table top in clamps after being glued together\"><\/figure><p>It worked out mostly ok, I guess? I think the result ended up better with the pocket screws of the other table (in terms of things being aligned). However, this was a better use of materials (fewer screws), was closer to how I feel it &ldquo;should&rdquo; be built (whatever that means), and was a challenge I wanted to try. Overall, it worked out.<\/p><h2 id=\"detail-change\">Detail Change <a href=\"#detail-change\">\ud83d\udd17<\/a><\/h2><p>One other thing I did was to be <em>extra fancy<\/em> with the profile of the table. I got out my fearful friend the router and used a roman ogee bit to give the top some definition and flair. It&rsquo;s&hellip;fine?<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pair_tables\/roman_ogee.jpg\" alt=\"The side profile of the top of the table, which has been cut with a router\"><\/figure><h2 id=\"deployment\">Deployment <a href=\"#deployment\">\ud83d\udd17<\/a><\/h2><p>These are currently in my enclosed porch, on the ends of some outdoor furniture we have.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pair_tables\/top_finished.jpg\" alt=\"A completed table in my porch\"><\/figure><p>And because I&rsquo;m sure you&rsquo;re very concerned, yes the drawers open and close.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/pair_tables\/drawer_open.jpg\" alt=\"A completed table in my porch\"><\/figure><p>What would you use these tables for?<\/p>"},{"title":"1,000 Miles","link":"https:\/\/kevinjmurphy.com\/posts\/1000_miles\/","pubDate":"Tue, 08 Oct 2024 14:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/1000_miles\/","description":"<h2 id=\"ebike-excitement\">eBike eXcitement <a href=\"#ebike-excitement\">\ud83d\udd17<\/a><\/h2><p>This evening, on the way home from gymnastics, with my daughter in the rear cargo seat, my eBike&rsquo;s odometer ticked over 1,000 miles. I was riding at the time, so you&rsquo;ll need to settle for the obligatory picture of it at 1,001 miles.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/1000_miles.jpg\" alt=\"My bike&#39;s odometer reading 1,001 miles\"><\/figure><p>And you&rsquo;ll have to trust me that by getting to 1,001 I first rode 1,000 miles.<\/p><p>We&rsquo;ve had the eBike for almost 14 months, and it&rsquo;s been the primary way my daughter and I get around town. I ride her into school every day on the bike, no matter how cold it is. We ride to the playground. We go to the library. We ride in to drop her off at summer camp in the morning. Maybe we&rsquo;ll go to get ice cream. We go to her activities. Stop to run an errand.<\/p><p>It&rsquo;s nothing exciting - and that&rsquo;s the point. It&rsquo;s just our normal mode of transportation now.<\/p><p>I still have a car and use it. It averages 30.70 miles per gallon (I track it every time I fill it up with gas). That&rsquo;s 32 gallons of gas I&rsquo;ve saved and instead used my legs (with pedal assist powered by the solar panels on my roof). That&rsquo;s not much, but it&rsquo;s something. And that&rsquo;s ok. I didn&rsquo;t get it to save the world, but I hope it&rsquo;s helping.<\/p><p>It&rsquo;s <em>fun<\/em>. It&rsquo;s a good way to get around town. My daughter enjoys it. She yells, &ldquo;bike lane!&rdquo; when we come across one of the (far too few) bike lines in our area. She likes climbing in the back all by herself. Here&rsquo;s to the next 1,000 miles with my daughter behind me.<\/p>"},{"title":"Office End Table Construction","link":"https:\/\/kevinjmurphy.com\/posts\/office-end-table-construction\/","pubDate":"Mon, 07 Oct 2024 14:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/office-end-table-construction\/","description":"<h2 id=\"building-in-3d\">Building in 3D <a href=\"#building-in-3d\">\ud83d\udd17<\/a><\/h2><p>I make a lot of things. Most commonly code. I <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">blog<\/a> (maybe you&rsquo;re aware, given you&rsquo;re here). I build <a href=\"https:\/\/kevinjmurphy.com\/speaking\/\">conference talks<\/a>. Sometimes manifestos. Most of these live on the computer. Sometimes I need to step away and not look at a screen.<\/p><p>On occasion, I&rsquo;ll build some piece of furniture we need in the house. Or some small accessory out of wood that I&rsquo;ll use on my desk. This is one such time, and I decided to document it.<\/p><p>Similar to my recent posts on <a href=\"https:\/\/kevinjmurphy.com\/tags\/music\/\">music<\/a> I&rsquo;m listening to, this isn&rsquo;t directly related to software development. If you want to bail because of that, no judgement.<\/p><h2 id=\"the-problem\">The Problem <a href=\"#the-problem\">\ud83d\udd17<\/a><\/h2><p>In my office, I have a couch. Right now there&rsquo;s a metal filing cabinet next to the couch that I&rsquo;m using as an end table. It&rsquo;s working fine, but I don&rsquo;t even have much paperwork in it. I wanted something different. This DOES show up in my video background. I thought it&rsquo;d be interesting to have something I&rsquo;ve built in my background, even just for myself.<\/p><p>On the other side of the couch there&rsquo;s a basket with a blanket that&rsquo;s on the floor. I wanted to move that off the floor. And a drawer to store whatever I want not on the tabletop.<\/p><h2 id=\"the-design\">The Design <a href=\"#the-design\">\ud83d\udd17<\/a><\/h2><p>The goal here is to be simple. I&rsquo;m not going to win any design awards. But it will get done. And reasonably quickly. I do my designing on paper. I could probably be more efficient and build cut lists and stuff by learning some piece of software. But part of the point here is NOT using my computer. And now ironically I&rsquo;m documenting this&hellip;on my computer.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/design.jpg\" alt=\"End table design on paper\"><\/figure><p>In a bold move, this will use four legs, two sides, a back, and a front. I&rsquo;ve really revolutionized the world of table building here. For the drawer, I&rsquo;m not going to have hardware to open it. Instead, I&rsquo;m going to cut a hole out of the drawer face itself. I toyed with making a few holes on the side. After consulting with my design partner (my daughter), we decided to land on one in the middle.<\/p><h2 id=\"the-materials\">The Materials <a href=\"#the-materials\">\ud83d\udd17<\/a><\/h2><p>This requires 1&quot;x6&quot; and 2&quot;x2&quot; lumber. For the 1x6s, I&rsquo;m using poplar. It&rsquo;s (relatively) cheap and (I think) a step up from soft pine. Some people don&rsquo;t like the look of poplar. I totally get that, but the majority of this will be painted anyway.<\/p><p>For the 2x2s, I&rsquo;m not actually going to purchase 2x2s. Instead, I&rsquo;ll grab the straightest 2x4 wall studs I can possibly find. I dig through the giant pallet of 2x4s at the big box store. It&rsquo;s more economical for me to do that.<\/p><p>The drawer box will use 3\/4&quot; plywood for the sides and 1\/4&quot; plywood for the bottom.<\/p><p>My main joinery method will be pocket screws. Pocket screws also get a bad reputation as not being &ldquo;real&rdquo; joinery. Yeah, gatekeeping doesn&rsquo;t only exist in software development. However, I will be using it for the following reasons:<\/p><ul><li><p>It&rsquo;s quick.<\/p><\/li><li><p>None of the joinery will be exposed.<\/p><\/li><li><p>It&rsquo;s quick.<\/p><\/li><li><p>I have the materials on hand.<\/p><\/li><li><p>It&rsquo;s quick.<\/p><\/li><li><p>I don&rsquo;t care what you think about my joinery expertise.<\/p><\/li><li><p>It&rsquo;s quick.<\/p><\/li><\/ul><p>One downside here with pocket screws is with how many 2x2s I&rsquo;m using. There isn&rsquo;t a space for multiple screws per side. That&rsquo;ll leave the 2x2s able to spin. That&rsquo;s a trade-off I&rsquo;m willing to make. Most of them will be up against other supports. And for the rest - I don&rsquo;t care.<\/p><h2 id=\"cutting-the-legs-and-supports\">Cutting the legs and supports <a href=\"#cutting-the-legs-and-supports\">\ud83d\udd17<\/a><\/h2><p>One reason I need to be quick is because of my constraints. I don&rsquo;t have a deadline, but I want to make progress. My workshop is my driveway. I need to build outside. I need light to build. I work during the day. The time between work ending and the rest of my evening home life starting leaves me with about an hour after work each day.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/workshop.jpg\" alt=\"My workshop, which is my driveway\"><\/figure><p>I purchase 2x4s, but I need 2x2s. And I don&rsquo;t want the rounded edges of a 2x4. I start by ripping the side of one of the 2x4s the full length of the stud. That gives me a clean, straight edge.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/cut_2x4.jpg\" alt=\"Ripping 2x4 on the table saw\"><\/figure><p>Then I set my rip fence based on the thickness of the 2x4 and make another rip cut the length of the board. That gives me one 2x2. Doing that again, making sure to cut off the rounded edge, gives me my other. I repeat that for each of the 2x4s.<\/p><p>I then use my miter saw to cut the 2x2s to their height for the table legs. You know they&rsquo;re legs because I&rsquo;ve labeled them accordingly.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/legs_labeled.jpg\" alt=\"4 table legs each with one letter &#39;LEGS&#39; on the top\"><\/figure><p>I cut down the front, back, and side support 2x2s the same way.<\/p><h2 id=\"cutting-the-top-sides-and-drawer-face\">Cutting the top, sides, and drawer face <a href=\"#cutting-the-top-sides-and-drawer-face\">\ud83d\udd17<\/a><\/h2><p>The poplar 1x6 boards need to get cut to length and I also use the miter saw to do that. To make repeatable cuts, I add an extension fence to my small miter saw. That is a fancy way of saying I screwed a board to the miter saw. And then clamp another piece of wood the distance I want to cut.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/miter_saw_fence.jpg\" alt=\"Miter saw with extension fence\"><\/figure><p>Even though I have plans with dimensions, it&rsquo;s not as important that I meet those exact dimensions. What is more important is that everything relatively is the same size. So, if something is supposed to be 17&quot;, it&rsquo;s not as problematic if it&rsquo;s actually 16+7\/8&quot;. As long as every other piece that&rsquo;s supposed to be 17&quot; is the same actual size, we&rsquo;re good.<\/p><p>The fence helps ensure that each of my pieces of that dimension are the same size, even if it&rsquo;s a little off.<\/p><p>Unfortunately with the 1x6 and the fence, my miter saw blade isn&rsquo;t big enough to cut the entire 1x6. So I need to cut (roughly) half of it, flip it over, then cut the rest. Without the fence, I could get the entire 1x6 cut on the miter saw. But, that&rsquo;s the cost I paid for repeatability.<\/p><h2 id=\"sanding-part-1\">Sanding, Part 1 <a href=\"#sanding-part-1\">\ud83d\udd17<\/a><\/h2><p>There&rsquo;s always a lot of sanding with projects. I start preemptively sanding everything. That&rsquo;ll make it easier to ensure I get everything sanded at least a little. Once it&rsquo;s put together, it may be hard to reach a corner or some other bit. By doing it now, I&rsquo;m getting a start on a smooth surface everywhere.<\/p><p>I use my workmate to lock in the 2x2&quot; pieces. That way they don&rsquo;t move around as much when sanding, given they&rsquo;re pretty slender.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/sanding_2x2.jpg\" alt=\"Sanding a 2x2\"><\/figure><p>After that, I have all my pieces cut and sanded.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/all_pieces_cut.jpg\" alt=\"All the individual pieces that need to be combined\"><\/figure><h2 id=\"side-construction\">Side Construction <a href=\"#side-construction\">\ud83d\udd17<\/a><\/h2><p>Now I can finally start putting anything together. I start with the sides. I drill pocket holes in the side supports and line one up against the legs. I use my speed square to give some sense of roughly installing it square. Then pocket screws go in the pocket holes, connecting the side support to the leg.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/line_up_side_pieces.jpg\" alt=\"Aligning the side support to the legs\"><\/figure><p>The sides next have a piece of poplar that will cover the drawer. I attach that to the legs with pocket screws as well. I prop up a piece of poplar below it, because it is half the thickness as the 2x2 side support. Then I add another 2x2 below it. This is kind of acting as trim, or a decorative piece. It doesn&rsquo;t add too much structure. But I like the symmetry.<\/p><p>The 2x2 at the bottom is helpful for giving some structure towards the bottom. It&rsquo;s also for supporting the shelf that&rsquo;ll sit on top of it.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/side_constructed.jpg\" alt=\"A completed side assembly\"><\/figure><h2 id=\"back-construction\">Back Construction <a href=\"#back-construction\">\ud83d\udd17<\/a><\/h2><p>The back mirrors the look of either side, with a 2x2, 1x6, 2x2, and bottom 2x2. I hold the sides up with clamps while attaching the back rails to each side assembly.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/attach_back.jpg\" alt=\"Attaching the two sides together\"><\/figure><h2 id=\"front-construction\">Front Construction <a href=\"#front-construction\">\ud83d\udd17<\/a><\/h2><p>The front has the 2x2s of the side and back. However, there is no 1x6 here. The drawer face will take that space eventually.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/front_construction.png\" alt=\"Attaching 2x2s to the front\"><\/figure><h2 id=\"table-top\">Table Top <a href=\"#table-top\">\ud83d\udd17<\/a><\/h2><p>The top of the table is going to be 4 1x6 boards joined together. I first inspect the boards to see which side I want to be on the top. I don&rsquo;t attempt to do any grain matching or anything. That wouldn&rsquo;t have worked out well anyway and also, this is getting painted.<\/p><p>Once I have that figured out, I flip all the boards over and start marking up the bottom side. That gives me reference points across the boards. Where lines match between boards tells me I&rsquo;m gluing them together at the right position.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/line_up_top.jpg\" alt=\"Marking the underside of the table top for alignment\"><\/figure><p>I could only glue the boards, but I also attach them with pocket screws. I could have chosen one or the other, but I chose both. This gives me a chance to document how to make the pocket holes. There&rsquo;s a special jig that attaches to the end of the board. And then I have a drill bit with a collar on it so it stops at a particular depth. I use that to drill into the jig, which leaves the angled holes on the face of the board.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/pocket_hole_jig.jpg\" alt=\"Pocket hole jig\"><\/figure><p>I use special pocket screws, inserted into the pocket hole, to attach the board to the other board.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/pocket_hole_screw.jpg\" alt=\"Attaching pocket screw\"><\/figure><p>In the case of this table top, I first apply glue before screwing the boards together. I use my two pipe clamps to keep pressure on everything. And use a spare 2x2 on the top to try to keep the top surface flat and level. I don&rsquo;t have a planer. Any adjustments I make here for boards being higher or lower than the board next to them will require sanding.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/top_glued_screwed.jpg\" alt=\"Clamping the glued and screwed table top\"><\/figure><p>Speaking of sanding, I sand down the top to get the excess glue that squeezed out and try to get a flat surface.<\/p><p>I left the top in the clamps for a day. Given the screws, it&rsquo;s much longer than necessary, but I had other things to do anyway, like work.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/top_sanding.jpg\" alt=\"Sanding the table top\"><\/figure><p>While I was already sanding, I sanded everything once again.<\/p><h2 id=\"bottom-shelf\">Bottom Shelf <a href=\"#bottom-shelf\">\ud83d\udd17<\/a><\/h2><p>I still have the bottom shelf to deal with. This was constructed the same way as the table top. However, I need to cut it down to match the dimensions of the table. I could measure, but again, the actual measurement isn&rsquo;t what matters. It&rsquo;s how it matches relative to the other items. So, I take the table, place it on my table saw, set one end of the table against the blade, and push the fence against the other end. That gives me the exact width of the table.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/shelf_dimension.jpg\" alt=\"Measuring the table dimensions by laying it on top of the table saw\"><\/figure><p>I use that to cut down the shelf to that same width. I repeat the process to get the depth.<\/p><p>The shelf needs to fit inside the table legs, so I need to cut out the corners. To get these measurements, I place the shelf on the top of the table and draw lines where the legs meet the shelf.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/shelf_cutout.jpg\" alt=\"Lining up the table legs with the bottom shelf to know where to cut\"><\/figure><p>I then extend the lines to the top and join them together with my pencil. That gives me the rectangles I need to remove from the corners.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/shelf_cutout_extension.jpg\" alt=\"Extending the lines on the side to draw a rectangle to cut\"><\/figure><p>I use a jigsaw to cut out these ends. Using the straight blade will give me a precise end. If I used the table saw, then the circular blade would need to cut through too much material on one side to get to the appropriate length. Plus, it would be tough to manage.<\/p><p>Sometimes, you do need to use the right tool for the job.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/jigsaw.jpg\" alt=\"Jigsaw on top of the shelf, ready to cut\"><\/figure><p>With that, I can fit the shelf in the table. As you can see, I initially cut too little and the shelf wouldn&rsquo;t fit. That is on purpose. It&rsquo;s a lot easier to remove more wood than it is to add it back. I continue shaving more off until it fits snugly.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/shelf_fit.jpg\" alt=\"The shelf does not yet fit inside the table legs\"><\/figure><h2 id=\"drawer-box\">Drawer Box <a href=\"#drawer-box\">\ud83d\udd17<\/a><\/h2><p>Drawers have been difficult for me to get right. Every piece I build that has drawers has some problem with the drawer. They&rsquo;re not quite square and they catch at some point. I measure wrong and they end up too short, so I need to shim out the drawer slides. Whatever the problem is, the commonality is that me and drawers don&rsquo;t get along. As much as I like things having drawers. So, here I go - building another drawer.<\/p><p>I start by getting my 3\/4&quot; plywood and cutting it to the height of my drawer - 5&quot;. It&rsquo;s a bit tall, but I figure if it ends up being too tall I can cut it down. I rip the plywood to that height on the table saw.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_box_height.jpg\" alt=\"Ripping plywood to the height of the drawer\"><\/figure><p>Then it&rsquo;s back to the miter saw with my extension fence to cut the two sides of the drawer. My fence is from an extra french cleat I have. That lets me use the angled cut on the top to lay my tape measure across so I can still see it while setting the stop block. That wasn&rsquo;t intentional, but was a nice surprise that I totally meant to do in retrospect (I didn&rsquo;t).<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/miter_saw_fence_tape.jpg\" alt=\"Miter saw extension fence with tape measure\"><\/figure><p>The width of the box is what I&rsquo;m most concerned with. I&rsquo;m going to use pocket screws to connect the drawer box. The two sides will run the full depth, and the front and back will be inside of them. The two drawer slides + the thickness of two pieces of plywood + the front\/back piece I&rsquo;m cutting == the space inside the table.<\/p><p>I grab the slides and pieces of plywood to measure how thick that all is. Then I subtract that from the space inside the table, and that is the distance I cut my front and back to. I again intentionally cut everything a little long. I try to shove it in the table, and then cut a little more until it is a comfortable fit.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_width_consideration.jpg\" alt=\"Measuring the thickness of the plywood and drawer slides\"><\/figure><p>Then I need to put the box together. I clamp one side to the edge of my table saw fence as a straight edge that&rsquo;s not going to move. And use my square to line things up as close to 90* as I can.<\/p><p>I do that for the front and back, on both sides. With that, I&rsquo;m left with a box.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_box_construction.jpg\" alt=\"Clamping drawer sides to the table saw to attach together\"><\/figure><p>My box probably needs a bottom though to actually STORE stuff in. I&rsquo;m going to use 1\/4&quot; plywood for that. And I need the exact dimensions of my box. So, what am I going to do? Yup. I put the box up on the table saw. I line up the blade against the box, push my fence out to that distance, and I know the exact size of that dimension.<\/p><p>I could have been fancier and cut dados (1\/4&quot; grooves) on the inside of all the pieces of the box. With that, the 1\/4&quot; panel could float freely in there. I do not do that. Instead, I&rsquo;m just nailing it in there. Which means I get to use my brad nailer.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/brad_nailer.jpg\" alt=\"Brad nailer on top of the drawer\"><\/figure><h2 id=\"drawer-install\">Drawer Install <a href=\"#drawer-install\">\ud83d\udd17<\/a><\/h2><p>The next step is to install the drawer slides on the table carcass. I flip the table on its side. I take a piece of the poplar that&rsquo;s the same thickness as the poplar that&rsquo;ll be my drawer face. And I make sure the drawer slides are inset that far in. Now the drawer will line up with the outside of the table when the drawer face is on it. I try to make sure it is straight&hellip;and screw the slide in. Then I do the same thing on the other side.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_slide_install.jpg\" alt=\"Aligning the drawer slide inside the table\"><\/figure><p>Now it&rsquo;s the moment of truth - attaching the drawer box to the slides. I use a piece of 1\/4&quot; plywood as a spacer against the bottom brace I have. Then the drawer won&rsquo;t rub against the support every time you open and close it.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/quarter_inch_clearance.jpg\" alt=\"A plywood panel resting on top of a 2x2 support\"><\/figure><p>The drawer box rests on top of that. I pull out the drawer slides and screw them in. I pull the inside of the slides out and screw in the end pieces. No pictures of that. I need all my hands to do that.<\/p><p>Then I pop the slides back in and&hellip;hold my breath.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_installed.jpg\" alt=\"Drawer box installed in the table\"><\/figure><p>It&rsquo;s alright. I think it&rsquo;s annoyingly one of the better drawers I&rsquo;ve made. It sticks a little on the left side (of that picture) about halfway in.<\/p><h2 id=\"drawer-face\">Drawer Face <a href=\"#drawer-face\">\ud83d\udd17<\/a><\/h2><p>Now, I need to work on the drawer face. That&rsquo;s going to be a piece of poplar, like is used for the sides of the table. And that&rsquo;s already been cut - but cut to EXACTLY fit in that slot. You don&rsquo;t want it to be exact. Then it&rsquo;ll rub against everything. So, very carefully, I trim not even a saw blade from the length and width.<\/p><p>I&rsquo;m not going to attach any knobs or hardware to this. To pull it out, I&rsquo;m going to cut out some of the drawer face as a pull. So, I get drawing on what&rsquo;ll be the back of the drawer face.<\/p><p>There&rsquo;s going to be an angle. It&rsquo;s not going to be square. But I can use my speed square to get that done still. I line it against the edge of the board, and use the markings to set my angle. I want a 30* angle. But from the direction the speed square is in the following picture, I&rsquo;m really subtracting that from 45* . So I line the edge of my board with the 15* marker.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/handle_angle.jpg\" alt=\"A speed square tilted to measure an angled cut\"><\/figure><p>I use my jigsaw to cut that out. And after finishing that, it&rsquo;s time to use what I think is the scariest tool - the router.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/router.jpg\" alt=\"A router\"><\/figure><p>A spinning blade that chews up wood, needs to go in the right direction, and can generally ruin your day right at the end. I want to use this because I don&rsquo;t want sharp edges that I&rsquo;m reaching my hand into to open the drawer. So I&rsquo;m going to use the router to round over the edges of the &ldquo;pull&rdquo;.<\/p><p>And my fear is justified - I set the depth a bit too far on the backside, so it cut into the face a bit. But, that&rsquo;s why you do it on the back first. I raise the blade a little and do the same on the other side.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/handle_router_roundover.jpg\" alt=\"The drawer face handle is cut too deep by the router\"><\/figure><p>I sand that all down next. No picture. It&rsquo;s sanding. You get it.<\/p><p>After that I need to get my drawer box back out. Because with that chunk taken out of the drawer face, I need to cut out a matching, but bigger, chunk of the drawer. Otherwise, you&rsquo;ll just run into the wall of the drawer with your hand and not actually be able to grab anything to open it.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/need_trim_drawer_box.jpg\" alt=\"Drawer face against drawer box shows we need to cut the drawer box to be able to open the drawer\"><\/figure><p>I cut that out on the jigsaw as well and end up with something like this.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_box_cut.jpg\" alt=\"Cutting out the front of the drawer box to leave space for the handle\"><\/figure><p>I sanded the edges of that down. It&rsquo;s not pretty, but it doesn&rsquo;t really matter. What it does mean is that I won&rsquo;t edge band the inside of the plywood that&rsquo;s facing up. That&rsquo;s a nice touch that just looks a little better, rather than seeing the plys of the plywood, but&hellip;it&rsquo;ll be fine.<\/p><p>Now I need to attach the drawer face to the drawer. I&rsquo;m going to screw from the inside of the drawer into the drawer face. This way the screws are on the inside of the drawer and not seen. I want the drawer face to have space around all the edges so it doesn&rsquo;t rub against the table. And ideally have it be centered. I use a deck of cards for that. I shove cards around all the edges, and make sure it&rsquo;s the same number of cards for the top\/bottom, and the same for each side.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/playing_card_offset.jpg\" alt=\"Playing cards inserted between the drawer face and the table for spacing\"><\/figure><h2 id=\"table-top-install\">Table Top Install <a href=\"#table-top-install\">\ud83d\udd17<\/a><\/h2><p>I have these figure 8 desktop connectors. It allows for some wood movement, but also just&hellip;gives you a way to connect them. I need to bury them inside the supports so everything is flush. So I grab a forstner bit in my drill and drill out a hole for them.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/figure_8_install.jpg\" alt=\"A drill making holes for the figure 8 desktop connector\"><\/figure><p>I screw them in so they&rsquo;re ready for the top to go on them. I want a consistent &ldquo;reveal&rdquo; (distance from the edge of the table top to the body of the table) so I measure that across all dimensions. Then I realize it&rsquo;s going to be a lot easier to screw everything in if I flip it over, so I do it again, and then screw the top in.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/top_install.jpg\" alt=\"The underside of the table top now attached to the table\"><\/figure><p>With that, we&rsquo;re done building. Of course, I&rsquo;m still not done. But, we&rsquo;ve done the fun part.<\/p><h2 id=\"paint-and-finish\">Paint and Finish <a href=\"#paint-and-finish\">\ud83d\udd17<\/a><\/h2><p>I hate painting. It&rsquo;s detail work I don&rsquo;t have the patience for and I&rsquo;m also just not good at it. I apply two coats of white paint and primer to all surfaces and one more coat of paint just on the top. My design consultant (my daughter) is not pleased with this color choice. She wants to go with &ldquo;rainbow&rdquo;. But, we have a gallon of spare white paint and have zero &ldquo;rainbow&rdquo; paint hanging around. So, white it is.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/painting.jpg\" alt=\"A coat of paint on the table\"><\/figure><p>I use polycrilic to finish the drawer face and the drawer itself. I also apply that to the table top after the paint is dry for more protection.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/drawer_box_poly.jpg\" alt=\"A coat of polycrilic on the drawer\"><\/figure><h2 id=\"deployment\">Deployment <a href=\"#deployment\">\ud83d\udd17<\/a><\/h2><p>There&rsquo;s only one thing that&rsquo;s left to do - place it in my office.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/end_table\/complete.jpg\" alt=\"The completed table next to a couch with headphones and a speaker on top of it\"><\/figure><p>It&rsquo;s not perfect, and neither am I. If we&rsquo;re on a video call in the near future and the table catches your eye, now you know where it came from. In extreme detail. Just in case you&rsquo;ve ever wanted to know all the steps I&rsquo;ve taken to build something.<\/p>"},{"title":"Frequently Played Oct 2024","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-10\/","pubDate":"Sun, 06 Oct 2024 18:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-10\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"21-days\">21 Days <a href=\"#21-days\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/pbqSaTagCqA?si=06m2BFoUx7n03uZv\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>In my experience, it takes more than 21 days.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-21-days-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I miss you most<br>In\u2005the\u2005morning<br>We used to\u2005talk<br>Over coffee<br>But now<br>I&rsquo;m gonna have to\u2005find<br>Another friend<br>When it&rsquo;s over<br>We do the leaving<br>We do the crying<br>We do the healing<br>And they say<br>21 days till I don&rsquo;t miss you<\/p><\/blockquote><h2 id=\"the-perfect-drug\">The Perfect Drug <a href=\"#the-perfect-drug\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/dn3j6-yQKWQ?si=sofKbf7to0005dn9\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Another entry that&rsquo;s Brian Fallon and NIN. I guess that&rsquo;s just where I&rsquo;m at these days.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-the-perfect-drug-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I got my heart, but my heart&rsquo;s no good<br>And you&rsquo;re the only one that&rsquo;s understood<br>&hellip;<br>Without you<br>Without you everything falls apart<br>Without you<br>It&rsquo;s not as much fun to pick up the pieces<\/p><\/blockquote>"},{"title":"RSpec Stubs The Object In Memory","link":"https:\/\/kevinjmurphy.com\/posts\/rspec-stubs-object-in-memory\/","pubDate":"Fri, 13 Sep 2024 15:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rspec-stubs-object-in-memory\/","description":"<h2 id=\"publish-or-perish\">Publish or Perish <a href=\"#publish-or-perish\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s say we&rsquo;ve been sitting on a handful of blog posts that are ready to publish, but we haven&rsquo;t released yet. This is a fictional story that in no way mirrors reality of any particular writer. Certainly not the author writing this post now. In a spirit of inspiration, we decide to write a small class to publish all these posts that exist in our system.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">DraftPost<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">publish_all<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Post<\/span><span style=\"color:#f92672\">.<\/span>draft<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Rather than actually publish them, we&rsquo;ve successfully procrastinated. We have a <em>way<\/em> to publish them, eventually. While we&rsquo;re busy not actually publishing them, let&rsquo;s test this method works.<\/p><h2 id=\"prolonged-publication-procrastination\">Prolonged Publication Procrastination <a href=\"#prolonged-publication-procrastination\">\ud83d\udd17<\/a><\/h2><p>We want to verify that we send the <code>publish<\/code> message to each of these posts. We don&rsquo;t actually want to execute the <code>publish<\/code> method. Perhaps that integrates with a third-party API. We don&rsquo;t want to manage <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/\">testing that interaction<\/a> here.<\/p><p>Instead, we&rsquo;ll stub out the response using <a href=\"https:\/\/rspec.info\/features\/3-12\/rspec-mocks\/\">RSpec mocks<\/a>.<\/p><p>With that goal, we write our test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;publishes all draft posts&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> draft <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Post<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">draft<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(draft)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">DraftPost<\/span><span style=\"color:#f92672\">.<\/span>publish_all<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(draft)<span style=\"color:#f92672\">.<\/span>to have_received(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This test <em>fails<\/em>. We put a breakpoint in our test. We confirm that a post with the same database ID as <code>draft<\/code> <strong>does<\/strong> get the <code>publish<\/code> method called on it.<\/p><p>Even though these objects pass the equality check, the issue is that they are <em>not<\/em> the same object in memory. Compare the <code>object_id<\/code> of the <code>draft<\/code> object to the object pulled out of the database. They&rsquo;re different. The post published in <code>DraftPost.publish_all<\/code> does not have the stub applied to it.<\/p><p>The stub operates on that <code>draft<\/code> object in memory, and only that object. Even though the other object is equal to the <code>draft<\/code> object, it is not the <em>same<\/em> as the <code>draft<\/code> object. Because of that, our assertion does not pass.<\/p><h2 id=\"any-instance-of\">Any Instance Of <a href=\"#any-instance-of\">\ud83d\udd17<\/a><\/h2><p>At this point, we may try to instead test what&rsquo;s returned from <code>publish_all<\/code> while still not having the <code>publish<\/code> method called on the posts.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;publishes all draft posts&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> draft <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Post<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">draft<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> allow_any_instance_of(<span style=\"color:#66d9ef\">Post<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:publish<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#e6db74\">&#34;called publish&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">DraftPost<\/span><span style=\"color:#f92672\">.<\/span>publish_all)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;called publish&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our test now passes. However, we have some drawbacks. First off, <a href=\"https:\/\/rspec.info\/features\/3-12\/rspec-mocks\/working-with-legacy-code\/any-instance\/\">RSpec itself<\/a> considers using <code>allow_any_instance_of<\/code> to be suspect.<\/p><blockquote><p>This feature is sometimes useful when working with legacy code, though in general we discourage its use for a number of reasons<\/p><\/blockquote><p>Now, this doesn&rsquo;t mean it&rsquo;s <em>wrong<\/em>. But, I&rsquo;d argue to only use it after carefully considering other options.<\/p><p>The other thing we changed here is our testing philosophy. Now we&rsquo;re testing what&rsquo;s returned by the method, rather than what the method does. And we did this by stubbing out what any Post should return when we call the <code>publish<\/code> method on it.<\/p><p>That again may be fine, but it may not test that we publish all the draft posts, which is our goal. Instead, it confirms what the <code>publish_all<\/code> method returns.<\/p><p>This may be hyperbolic, but if we changed the implementation to look like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">DraftPost<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">publish_all<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;called publish&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now our test that asserts this method is calling <code>publish<\/code> still passes, but we&rsquo;re definitely not publishing any posts.<\/p><h2 id=\"change-the-implementation\">Change the Implementation <a href=\"#change-the-implementation\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s change <code>publish_all<\/code> so we pass all the posts to publish into it.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">DraftPost<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">publish_all<\/span>(posts)<\/span><\/span><span style=\"display:flex;\"><span> posts<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now we can use the RSpec mock as we want.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;publishes all draft posts&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> draft <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Post<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">draft<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(draft)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">DraftPost<\/span><span style=\"color:#f92672\">.<\/span>publish_all(<span style=\"color:#f92672\">[<\/span>draft<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(draft)<span style=\"color:#f92672\">.<\/span>to have_received(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This test passes, and as a bonus, we don&rsquo;t need to access the database in our test anymore. What a speed improvement!<\/p><p>Once again, this choice comes with some careful consideration. First off, modifying the implementation to satisfy the way we want to test it may be undesirable. I do want to point out that I don&rsquo;t think this is true generally. I do believe in using your tests as your first consumer of your code. If something is tough to test, it may be hard to use or confusing to understand. There&rsquo;s value in receiving that signal and deciding to change the code&rsquo;s design or structure in that case.<\/p><p>Stepping back here, passing in the posts makes the name of this class pretty meaningless. This should only work on draft posts. However, we can pass any kind of post (really, anything that responds to <code>publish<\/code>) and this will still work.<\/p><p>Maybe we want something that&rsquo;ll generically publish any kind of post. If so, we probably shouldn&rsquo;t have that inside a <code>DraftPost<\/code> class. If this should only operate on draft posts, then this may not be the choice we want to make.<\/p><p>Passing in the posts allows our stub to work. Passing in the posts may allow us to achieve some purity of using dependency injection. Passing in the posts may make this method less usable. Enough to the point where perhaps it shouldn&rsquo;t exist at all.<\/p><h2 id=\"stub-the-db-interaction\">Stub the DB Interaction <a href=\"#stub-the-db-interaction\">\ud83d\udd17<\/a><\/h2><p>We still want to use the mock. We don&rsquo;t want to use RSpec features that the core team themselves recommend against. We don&rsquo;t want to change the implementation. It turns out we like the benefit of not needing to access the database. We can achieve all those goals by <em>also<\/em> stubbing out the database call.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;publishes all draft posts&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> draft <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Post<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">draft<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">Post<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:draft<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#f92672\">[<\/span>draft<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(draft)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">DraftPost<\/span><span style=\"color:#f92672\">.<\/span>publish_all<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(draft)<span style=\"color:#f92672\">.<\/span>to have_received(<span style=\"color:#e6db74\">:publish<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"finally-publishing-the-post\">Finally Publishing The Post <a href=\"#finally-publishing-the-post\">\ud83d\udd17<\/a><\/h2><p>We confirm that we send the right message (<code>publish<\/code>) without invoking its implementation. We do give up some confidence in that we don&rsquo;t know for sure that <code>Post.draft<\/code> works how we expect. We can regain that confidence by having other tests specific for that method.<\/p><p>One could argue this gives us more flexibility. If the implementation of <code>Post.draft<\/code> changes, this test will continue to work. It can get posts from a database or third-party API. Our method doesn&rsquo;t care.<\/p><p>On the flip side, one could also argue that this test is tying us to a particular implementation. If <code>DraftPost.publish_all<\/code> changed to instead use <code>Post.where(draft: true)<\/code>, then this test will no longer work. Even though using the scope or using where will retrieve the same records from the database.<\/p><p>Unfortunately, there is no <em>right<\/em> answer. We must consider the trade-offs we&rsquo;re willing to make to achieve our goal. What \u2728magic\u2728 you&rsquo;re interested in maintaining is up to you.<\/p><p>Keep in mind that RSpec mocks only work on that particular object in memory. If the implementation pulls the same record fresh out of the database, the mock will not apply. When using RSpec mocks in your tests, objects in memory may be farther than they appear.<\/p>"},{"title":"Frequently Played Sept 2024","link":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-09\/","pubDate":"Tue, 10 Sep 2024 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/frequently-played-2024-09\/","description":"<h2 id=\"frequently-played\">Frequently Played <a href=\"#frequently-played\">\ud83d\udd17<\/a><\/h2><p>I tend to listen to the same songs or albums on repeat that are evocative of how I&rsquo;m feeling or what&rsquo;s going on with me. Here is what I&rsquo;m currently listening to over, and over, and over, and over, again.<\/p><h2 id=\"the-blues-mary\">The Blues, Mary <a href=\"#the-blues-mary\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/d5hJV3dM5OA?si=ZVDhPccPCyvMoFxf\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>Brian Fallon may be best known as the lead singer of The Gaslight Anthem. I appreciate his solo work during their hiatus too. I think future entries of this series will feature a lot of Brian&rsquo;s output.<\/p><p><a href=\"https:\/\/genius.com\/Brian-fallon-the-blues-mary-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>I learned how to cry<br>In them lonesome September nights<br>I learned to get by<br>With the dogs and the dirt and the charm of the street<br>I fell asleep most nights<br>With your pictures right behind my eyes, your eyes<br>&hellip;<br>And cause your smile<br>Is bright like the ramparts on the 4th of July<br>And my baby swings like a boxer<br>And sends her right hooks under my chin<br>She cries like a baby<br>And she wears me just like a ring<br>Cries like a baby<br>And she wears me just like a ring<br>I&rsquo;d rather go than watch you leave<\/p><\/blockquote><h2 id=\"and-all-that-could-have-been\">And All That Could Have Been <a href=\"#and-all-that-could-have-been\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2U0flA_Yp64?si=k2V_SmRzUMzxsTf3\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><p>This is my favorite Nine Inch Nails song. However, I can only listen to it when I&rsquo;m in a certain mood. And when I do listen to it, it&rsquo;s on repeat.<\/p><p><a href=\"https:\/\/genius.com\/Nine-inch-nails-and-all-that-could-have-been-lyrics\">Full Lyrics<\/a><\/p><blockquote><p>Please<br>Take this<br>And run far away<br>Far as you can see<br>I am tainted<br>And happiness<br>And peace of mind<br>Were never meant for me<br>All these pieces<br>And promises<br>And left-behinds<br>If only I could see<br>In my nothing<br>You meant everything<br>Everything to me<\/p><\/blockquote>"},{"title":"Access Request Headers in a Rails Controller","link":"https:\/\/kevinjmurphy.com\/posts\/access-request-headers-in-rails-controller\/","pubDate":"Thu, 11 Jul 2024 18:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/access-request-headers-in-rails-controller\/","description":"<h2 id=\"heads-up\">Heads Up <a href=\"#heads-up\">\ud83d\udd17<\/a><\/h2><p>A coworker presented a failing request spec. They asked if they were passing headers incorrectly in the test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;reports to be a teapot when asked to brew coffee&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> headers <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">&#34;X-COMMAND&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;brew coffee&#34;<\/span> }<\/span><\/span><span style=\"display:flex;\"><span> get drinks_url, <span style=\"color:#e6db74\">headers<\/span>: headers<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response<span style=\"color:#f92672\">.<\/span>status)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">418<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>They wrote the test exactly like I&rsquo;d <a href=\"https:\/\/rspec.info\/features\/6-0\/rspec-rails\/request-specs\/request-spec\/\">expect<\/a>. But, rather than providing the 418, a 200 OK was the status code. I then looked at the controller this request spec was accessing.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">index<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> headers<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;X-COMMAND&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">&#34;brew coffee&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> head <span style=\"color:#ae81ff\">418<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @drinks <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Drink<\/span><span style=\"color:#f92672\">.<\/span>all<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Nothing <em>obvious<\/em> caught my attention. But now that I&rsquo;d been effectively <a href=\"https:\/\/xkcd.com\/356\/\">nerd sniped<\/a>, I had to figure out what was going on.<\/p><h2 id=\"heading-in-for-a-closer-look\">Heading In For a Closer Look <a href=\"#heading-in-for-a-closer-look\">\ud83d\udd17<\/a><\/h2><p>I added a breakpoint inside the controller to inspect the headers when the test was running.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">irb<\/span>:<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> headers<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span>{<span style=\"color:#e6db74\">&#34;X-Frame-Options&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;SAMEORIGIN&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;X-XSS-Protection&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;0&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;X-Content-Type-Options&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;nosniff&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;X-Download-Options&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;noopen&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;X-Permitted-Cross-Domain-Policies&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;none&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Referrer-Policy&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;strict-origin-when-cross-origin&#34;<\/span>}<\/span><\/span><\/code><\/pre><\/div><p>As expected, given the failing test, the <code>X-COMMAND<\/code> header was nowhere to be found. But luckily, they did seem familiar to me. They looked to be Rails&rsquo; <a href=\"https:\/\/github.com\/rails\/rails\/blob\/f1aa436d738af1852b610189aeb93a5609bfe3b0\/actionpack\/lib\/action_dispatch\/railtie.rb#L30\">default headers<\/a>. But those <a href=\"https:\/\/edgeguides.rubyonrails.org\/configuring.html#config-action-dispatch-default-headers\">default headers<\/a> are for the <strong>response<\/strong>, not the <em>request<\/em>.<\/p><p>I still had my console session with my breakpoint, so I asked what kind of headers we were interacting with.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">irb<\/span>:<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> headers<span style=\"color:#f92672\">.<\/span>class<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Response<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Header<\/span><\/span><\/span><\/code><\/pre><\/div><p>This confirmed we were dealing with the response, not request, headers.<\/p><h2 id=\"heads-down\">Heads Down <a href=\"#heads-down\">\ud83d\udd17<\/a><\/h2><p>I needed to trace my way backwards from what I have or know. I asked what defines the headers method by asking for the <a href=\"https:\/\/ruby-doc.org\/3.2.1\/Method.html#method-i-source_location\">source location<\/a>. That&rsquo;ll tell me the file and line number.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">irb<\/span>:<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> method(<span style=\"color:#e6db74\">&#34;headers&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>source_location<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;...\/gems\/actionpack-7.0.3.1\/lib\/action_controller\/metal.rb&#34;<\/span>, <span style=\"color:#ae81ff\">147<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p><a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/actionpack\/lib\/action_controller\/metal.rb#L147\">That line<\/a> shows <code>headers<\/code> delegated to an <a href=\"https:\/\/guides.rubyonrails.org\/active_support_core_extensions.html#internal-attributes\">internal attribute<\/a> <code>@_response<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>delegate <span style=\"color:#e6db74\">:headers<\/span>, <span style=\"color:#e6db74\">:status<\/span><span style=\"color:#f92672\">=<\/span>, <span style=\"color:#e6db74\">:location<\/span><span style=\"color:#f92672\">=<\/span>, <span style=\"color:#e6db74\">:content_type<\/span><span style=\"color:#f92672\">=<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:status<\/span>, <span style=\"color:#e6db74\">:location<\/span>, <span style=\"color:#e6db74\">:content_type<\/span>, <span style=\"color:#e6db74\">:media_type<\/span>, <span style=\"color:#e6db74\">to<\/span>: <span style=\"color:#e6db74\">&#34;@_response&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>That internal attribute is accessible in the controller by calling <code>response<\/code>. We can see that from the <code>attr_internal<\/code> definition on <a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/actionpack\/lib\/action_controller\/metal.rb#L145\">line 145<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>attr_internal <span style=\"color:#e6db74\">:response<\/span>, <span style=\"color:#e6db74\">:request<\/span><\/span><\/span><\/code><\/pre><\/div><p><code>response<\/code> isn&rsquo;t the ONLY internal attribute on that line though. There&rsquo;s ALSO a <code>request<\/code>. In our console, let&rsquo;s see what that request is.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">irb<\/span>:<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> request<span style=\"color:#f92672\">.<\/span>class<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Request<\/span><\/span><\/span><\/code><\/pre><\/div><p>That class also <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActionDispatch\/Request.html#method-i-headers\">responds to<\/a> <code>headers<\/code>, providing the <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActionDispatch\/Http\/Headers.html\">request headers<\/a>.<\/p><h2 id=\"heading-in-for-the-close\">Heading In For The Close <a href=\"#heading-in-for-the-close\">\ud83d\udd17<\/a><\/h2><p>The change to get our test to pass is small. We don&rsquo;t want the response headers, which is what the <code>headers<\/code> variable is. We need the request headers, which are accessible at <code>request.headers<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">index<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> request<span style=\"color:#f92672\">.<\/span>headers<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;X-COMMAND&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">&#34;brew coffee&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> head <span style=\"color:#ae81ff\">418<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @drinks <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Drink<\/span><span style=\"color:#f92672\">.<\/span>all<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now that we&rsquo;re accessing the headers of the <strong>request<\/strong> our test passes.<\/p><p>Naming is hard. Asking for a controller&rsquo;s headers could be either the request or the response headers. Turns out, Rails will give you the response headers. To access the request headers, explicitly ask for them from the request object.<\/p>"},{"title":"Four Things To Take Away From RailsConf 2024","link":"https:\/\/kevinjmurphy.com\/posts\/4-things-railsconf-2024\/","pubDate":"Mon, 13 May 2024 19:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/4-things-railsconf-2024\/","description":"<h2 id=\"railsconf-2024\">RailsConf 2024 <a href=\"#railsconf-2024\">\ud83d\udd17<\/a><\/h2><p>Now that RailsConf 2024 is behind us, I want to share some key takeaways that I left Detroit with. I also have a <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-recap\/\">full recap<\/a> available of what I saw while I was there.<\/p><h2 id=\"1-rubyfriends-is-more-than-a-hashtag\">1. #RubyFriends Is More Than A Hashtag <a href=\"#1-rubyfriends-is-more-than-a-hashtag\">\ud83d\udd17<\/a><\/h2><p>I cannot overstate the importance of your professional network. In the Ruby community, we talk a lot about Ruby Friends. How you build and cultivate that group is up to you. Conferences have been a main driver of that forme.<\/p><p>The reason I was on the <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/\">Program Committee<\/a> is because of the conference relationships I\u2019ve built. I congratulated Ufuk on joining the Ruby Central board at RubyConf last year. I (sincerely) told him to keep me in mind if I can help in the future. He immediately said, &ldquo;program committee?&rdquo; And then he remembered when putting together RailsConf&rsquo;s committee. I&rsquo;m thankful to him for the opportunity.<\/p><p>The conference is an excuse to connect with my friends before, during, and after the actual event. I can check in on speaker friends throughout the process. This year I developed meaningful relationships with fellow Program Committee members. That persists throughout the conference. And after we can reminisce and recall all those moments.<\/p><p>And sure, hopefully I&rsquo;m being helpful to my Ruby Friends, much like I know they&rsquo;ll help me. But really what I appreciate most is having something to reach out to them about.<\/p><h2 id=\"2-ruby--rails\">2. Ruby \u2764\ufe0f Rails <a href=\"#2-ruby--rails\">\ud83d\udd17<\/a><\/h2><p>Rails benefits greatly from the tooling and improvements of Ruby itself. John Hawthorn demonstrated how Vernier can help profile Ruby code. That benefits all our Rails applications. Aaron Patterson described existing and future changes in Ruby. Each one improves our Rails applications. To take advantage of them, we need to stay up-to-date with those changes.<\/p><h2 id=\"3-build-with-rails-build-with-rails\">3. Build with Rails? Build With Rails. <a href=\"#3-build-with-rails-build-with-rails\">\ud83d\udd17<\/a><\/h2><p>The theme at RailsConf was &ldquo;Building with Rails&rdquo;. We heard from people who built businesses with Rails. Built teams with Rails. Built technical advances with Rails. Built careers with Rails. Nadia talked to us about The StoryGraph. I took away an appreciation of how many risks and unknowns you take on starting and running a business. By keeping the tech simple and known, you can allocate your risk budget elsewhere.<\/p><p>Irina also surveyed various earlier-stage founders on their use of Rails. Hearing what they appreciate about the ecosystem was reaffirming. Learning what they&rsquo;re missing was informative and helpful.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_irina_dont_be_shy.jpg\" alt=\"Irina at RailsConf 2024 with a slide that says, &#39;Whatever you do don&#39;t be shy about building with Rails&#39;\"><\/figure><p>Marco also called attention to Rails-adjacent projects, like Turbo and Stimulus Reflex. His talk provided a great history lesson on these projects. He gave exciting introductions to new tooling. He shared learning resources. Marco gave many options for how we can leverage this all in our Rails applications.<\/p><h2 id=\"4-its-almost-over\">4. It&rsquo;s (Almost) Over <a href=\"#4-its-almost-over\">\ud83d\udd17<\/a><\/h2><p>Yes, RailsConf 2024 is definitely over. However, RailsConf itself is also almost over. RailsConf 2025 is the <a href=\"https:\/\/rubycentral.org\/news\/anewearforrubycentralevents\/\">last planned RailsConf<\/a>. Ruby Central will continue supporting RubyConf.<\/p><p>This will free RubyCentral up to focus their efforts. There are many other important benefits they provide the community. I know for a long time I thought of Ruby Central as the group the puts on the conferences. I took for granted the work they do. I didn\u2019t consider how vital they are. They support the bedrock tooling for our community: Bundler and RubyGems. I hope this shift will help others be more awareness to the rest of the ways Ruby Central supports Ruby.<\/p><p>But we&rsquo;re not done yet. Ruby Central needs your help shaping RailsConf 2025. Fill out <a href=\"https:\/\/docs.google.com\/forms\/d\/e\/1FAIpQLSeQIVh1Uje6LBHFbkcSgVMliMPUVKt-kVloHAQ8OuVkvYoopw\/viewform?ref=rubycentral.org\">this survey<\/a> to tell them where you&rsquo;re excited to see RailsConf. That&rsquo;s right, there isn&rsquo;t a date or location yet. Wherever it is, I hope I see you there.<\/p>"},{"title":"RailsConf 2024 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-recap\/","pubDate":"Sun, 12 May 2024 09:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-recap\/","description":"<h2 id=\"railsconf-2024\">RailsConf 2024 <a href=\"#railsconf-2024\">\ud83d\udd17<\/a><\/h2><p>RailsConf recently wrapped up in Detroit, Michigan. This post is meant to highlight the great work from all involved. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you once they are available. Unfortunately, I couldn&rsquo;t be everywhere, so this covers what I saw.<\/p><p>I&rsquo;ve separately compiled a list of <a href=\"https:\/\/kevinjmurphy.com\/posts\/4-things-railsconf-2024\/\">brief takeaways<\/a>.<\/p><h2 id=\"preparing\">Preparing <a href=\"#preparing\">\ud83d\udd17<\/a><\/h2><p>I had a bit of work to do leading up to the conference. It was my honor andpleasure to serve on the <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/\">Program Committee<\/a> for RailsConf this year. Mainly this meant reading all of the proposals. And then the even more difficult work to whittle that down to our <a href=\"https:\/\/railsconf.org\/speakers\/\">program<\/a>.<\/p><p>The committee broke into smaller groups. I was on the tracks group, even though we didn&rsquo;t have <a href=\"https:\/\/kevinjmurphy.com\/posts\/tracks-not-at-railsconf-2024\/\">formal tracks<\/a>. We helped put the schedule together. We provided feedback to those who asked for it about their proposal. I also tried to be as available as possible to any other groups who needed a hand.<\/p><p>Just as I&rsquo;ve done in the past, I joined Ruby Central members for the CFP coaching sessions. We helped others with their proposals in small groups. This was a great opportunity to meet with prospective speakers and workshop ideas to submit for the conference. I&rsquo;m glad that Ruby Central runs these events. They&rsquo;re a great opportunity for prospective speakers. If you&rsquo;re considering submitting to a future conference, I hope you&rsquo;ll join one in the future.<\/p><p>Julie J, co-host of the Ruby for All podcast, was also on the Program Committee. Julie and I joined her co-host Andrew Mason to <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2024\/\">talk about<\/a> our experience on the committee.<\/p><p>I was also happy to be a speaker mentor. I paired with <a href=\"https:\/\/railsconf2024.sessionize.com\/speaker\/45d44f90-cd1f-4678-9a87-17c44e3d9900\">Dawn Richardson<\/a>. Dawn had a <a href=\"https:\/\/railsconf2024.sessionize.com\/session\/623026\">terrific talk about<\/a> her experience as a Principal Engineer. Dawn was ready with a great topic, talk structure, and plans for delivery before we met. I would have never guessed she was a first-time speaker unless she told me! Definitely check out her talk if you didn&rsquo;t see it at RailsConf.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><h3 id=\"opening-keynote\">Opening Keynote <a href=\"#opening-keynote\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/648347\">Nadia Odunayo<\/a> got us started with a reminder of how far Rails can take you. And more generally, how to decide on where to allocate risk. When starting a business, there is so much unknown. Keep the tech simple.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_nadia.jpg\" alt=\"Nadia at RailsConf 2024\"><\/figure><h3 id=\"so-writing-tests-feels-painful-what-now\">So writing tests feels painful. What now? <a href=\"#so-writing-tests-feels-painful-what-now\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/628784\">Stephanie Minn<\/a> walked us through an illustrative example of when testing can be painful. We built the test together, allowing us to understand the justification for why the test was written this way. We felt what it&rsquo;s like to realize we need a change and why. And of course, we worked to improve the situation. I am patiently awaiting the launch of Petreon, so I can sign up as Hickory&rsquo;s Club President.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_stephanie.jpg\" alt=\"Stephanie at RailsConf 2024\"><\/figure><h3 id=\"ruby-on-fails---effective-error-handling-with-rails-conventions\">Ruby on Fails - effective error handling with Rails conventions <a href=\"#ruby-on-fails---effective-error-handling-with-rails-conventions\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/626598\">Talysson Oliveira Cassiano<\/a> opened with an exceptional introduction to errors and exceptions. Later they introduced some anti-patterns for error handling before wrapping up with suggestions for effective error handling.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_talysson.jpg\" alt=\"Talysson at RailsConf 2024\"><\/figure><h3 id=\"crafting-rails-plugins\">Crafting Rails Plugins <a href=\"#crafting-rails-plugins\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/626652\">Chris Oliver<\/a> gave a tour on how to construct a Rails plugin for distribution. He demystified this process, coming at it from the perspective of someone already familiar with Rails. It turns out, you may be more prepared to build a plugin than you think.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_chris.jpg\" alt=\"Chris at RailsConf 2024\"><\/figure><h3 id=\"from-cryptic-error-messages-to-rails-contributor\">From Cryptic Error Messages To Rails Contributor <a href=\"#from-cryptic-error-messages-to-rails-contributor\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/618628\">Collin Jilbert<\/a>&rsquo;s teaching style translated just as well to the stage. He comfortably walked through an explanation of an issue all the way through to the fix.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_collin.jpg\" alt=\"Collin at RailsConf 2024\"><\/figure><h3 id=\"riffing-on-rails-sketch-your-way-to-better-designed-code\">Riffing on Rails: sketch your way to better designed code <a href=\"#riffing-on-rails-sketch-your-way-to-better-designed-code\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/630464\">Kasper Timm Hansen<\/a> explored code design live on stage. Working within the constraints of a single file (not necessarily class) focused our efforts. Removing the tie to any existing code allowed us to freely consider a world of possibilities.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_kaspar.jpg\" alt=\"Kasper at RailsConf 2024\"><\/figure><h3 id=\"closing-keynote-startups-on-rails\">Closing Keynote: Startups on Rails <a href=\"#closing-keynote-startups-on-rails\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/629524\">Irina Nazarova<\/a> ended our day with stories of current founders using Rails. Irina shared how Rails has been a <a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">competitive advantage<\/a>. We also explored what needs these founders have that aren&rsquo;t being met by the technology or the community.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_irina.jpg\" alt=\"Irina at RailsConf 2024\"><\/figure><h2 id=\"day-2---hack-day\">Day 2 - Hack Day! <a href=\"#day-2---hack-day\">\ud83d\udd17<\/a><\/h2><p>I left this day open in my planning. I did not attend any of the workshops. I started the day meandering through the hack day tables. I asked people what they were working on. I observed the conversation.<\/p><p>Later, I sat down with a friend to look at <a href=\"https:\/\/github.com\/kevin-j-m\/clockwork-test\">code<\/a> I had written nine years ago. We revisited the project with multiple sets of new eyes. Some who&rsquo;ve never seen it before. Others haven&rsquo;t seen it in a long, long time. We considered if this code even needs to exist. We had a good discussion on alternative implementations. We went through an interesting thought experiment to consider how I would or wouldn&rsquo;t write this code differently now.<\/p><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><h3 id=\"revisiting-the-hotwire-landscape-after-turbo-8\">Revisiting the Hotwire Landscape after Turbo 8 <a href=\"#revisiting-the-hotwire-landscape-after-turbo-8\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/630406\">Marco Roth<\/a> fit the past, present, and future into a tight package. It didn&rsquo;t feel rushed or consolidated. Because of this, I better understand where we are as a community for building complex UI interactions. I&rsquo;m equipped with more tools and resources to develop them. I&rsquo;m informed on where we&rsquo;re going.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_marco.jpg\" alt=\"Marco at RailsConf 2024\"><\/figure><h3 id=\"the-very-hungry-transaction\">The Very Hungry Transaction <a href=\"#the-very-hungry-transaction\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/626442\">Daniel Colson<\/a> brought some delightful storytelling to explain a technical topic. Small changes in transactions may seem innocuous but have drastic consequences. Code in transactions needs additional consideration we don&rsquo;t normally apply outside a transaction.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_daniel.jpg\" alt=\"Daniel at RailsConf 2024\"><\/figure><h3 id=\"dungeons--dragons--rails\">Dungeons &amp; Dragons &amp; Rails <a href=\"#dungeons--dragons--rails\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/630449\">Jo\u00ebl Quenneville<\/a> and Glittersense the gnome demonstrated the magic of Turbo (and stagecraft). The talk is worth the watch for the entertainment value alone. That it provides a delightful survey of the tools in the Turbo toolbox is a bonus.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_joel.jpg\" alt=\"Jo\u00ebl at RailsConf 2024\"><\/figure><h3 id=\"from-rspec-to-jest-javascript-testing-for-rails-devs\">From RSpec to Jest: JavaScript testing for Rails devs <a href=\"#from-rspec-to-jest-javascript-testing-for-rails-devs\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/623122\">Stefanni Brasil<\/a> dared to bring us out of our comfort zone. We accepted the reality that JavaScript exists and explored how to bring our rubyist testing practices to that environment.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_steffani.jpg\" alt=\"Stefanni at RailsConf 2024\"><\/figure><h3 id=\"beyond-senior-lessons-from-the-technical-career-path\">Beyond senior: lessons from the technical career path <a href=\"#beyond-senior-lessons-from-the-technical-career-path\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/623026\">Dawn Richardson<\/a> walked us up the career ladder. By explaining her experience as a Principal Engineer, we&rsquo;re now better equipped for our own progression.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_dawn.jpg\" alt=\"Dawn at RailsConf 2024\"><\/figure><h3 id=\"closing-keynote\">Closing Keynote <a href=\"#closing-keynote\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/railsconf2024.sessionize.com\/session\/648380\">Aaron Patterson<\/a> explained changes in Ruby, both current and coming, that can inform how we structure and write our Ruby code in a Rails app.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_aaron.jpg\" alt=\"Aaron at RailsConf 2024\"><\/figure><h2 id=\"appreciation\">Appreciation <a href=\"#appreciation\">\ud83d\udd17<\/a><\/h2><p>Thank you <a href=\"https:\/\/rubycentral.org\">Ruby Central<\/a> for your work to organize these events and supporting the Ruby community all year round.<\/p><p>Thank you to Andy and Ufuk for inviting me on the Program Committee. Thank you to the rest of the Program Committee: Julie, Ifat, Mayra, Aji, Gary, John, and Zarela.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_program_committee.jpg\" alt=\"The Program Committee Slide at RailsConf 2024\"><\/figure><p>Thank you to the Scholarship Committee, volunteers, and venue staff for bringing this conference to life.<\/p><p>Thank you sponsors for supporting the Ruby community and making it possible for us to come together.<\/p><p>Thanks to all the #RubyFriends, old and new, that I met in Detroit.<\/p><p>Thank you for reading to the end. Maybe we met at RailsConf 2024. Maybe we didn&rsquo;t. I hope to see you in 2025. Let&rsquo;s wrap up the long tradition of RailsConf with the send-off it deserves.<\/p>"},{"title":"Another Ruby for All Podcast Guest Appearance","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2024\/","pubDate":"Sat, 20 Apr 2024 20:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2024\/","description":"<h2 id=\"ruby-for-all-podcast\">Ruby for All Podcast <a href=\"#ruby-for-all-podcast\">\ud83d\udd17<\/a><\/h2><p>I was a guest on <a href=\"https:\/\/www.rubyforall.com\/s2\/66\">episode 66<\/a> of the Ruby for All podcast. Julie and I are both on the RailsConf 2024 Program Committee. I joined Julie and Andrew to talk about RailsConf 2024 and what it&rsquo;s been like being on the Program Committee.<\/p><p>From the episode description:<\/p><blockquote><p>In this episode of &lsquo;Ruby For All&rsquo;, hosts Andrew and Julie welcome guest Kevin Murphy, Software Developer at Pubmark and member of the RailsConf program committee. The discussion kicks off with Andrew and Julie catching up, then transitions into an in-depth conversation about the RailsConf planning process. Kevin and Julie, the Speaker Liaison, share insights into the workings of the program committee, the selection criteria for conference talks, and the challenges and rewards of organizing RailsConf. Additionally, Kevin elaborates on his role in the committee, the theme for this year&rsquo;s conference, and his goals for impact, and Julie looks forward to supporting speakers and managing workshops. The episode emphasizes the importance of volunteer contributions to the success of RailsConf and encourages attendees to express their gratitude to the organizers, and to go check out all the details at RailsConf.org and buy your tickets now!<\/p><\/blockquote><p>Give it a <a href=\"https:\/\/www.rubyforall.com\/s2\/66\">listen<\/a> and let me know what youthink!<\/p>"},{"title":"Making a (Sidekiq) Batch Recipe","link":"https:\/\/kevinjmurphy.com\/posts\/making-a-sidekiq-batch-recipe\/","pubDate":"Sat, 13 Apr 2024 18:00:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/making-a-sidekiq-batch-recipe\/","description":"<h2 id=\"the-right-number-of-cooks-in-the-kitchen\">The Right Number of Cooks in the Kitchen <a href=\"#the-right-number-of-cooks-in-the-kitchen\">\ud83d\udd17<\/a><\/h2><p>Today we&rsquo;re going to make a stew. The recipe has three steps that can all run independently. But when they&rsquo;re done, their output needs to come together to finish the stew.<\/p><p>We&rsquo;ll set each step up as a separate Sidekiq job. The details of each step aren&rsquo;t important for this demonstration.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GetRawVeggiesWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GetBaconWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GetCupOfSoupWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can enqueue these to run by themselves no problem. However, we need to know when they&rsquo;re all done so we can finish our recipe. We can group these together using a <a href=\"https:\/\/sidekiq.org\/products\/pro.html\">Sidekiq Pro<\/a> feature: batches.<\/p><p>We&rsquo;ll write a series of RSpec tests to explore how to use batches to make our recipe.<\/p><h2 id=\"tracking-kitchen-progress\">Tracking Kitchen Progress <a href=\"#tracking-kitchen-progress\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;ll start by creating a batch, and adding our recipe steps to it as jobs. Just like a Sidekiq job has a <code>jid<\/code> (job ID), a batch has a <code>bid<\/code> (batch ID). We can use that <code>bid<\/code> to check on the batch&rsquo;s status thanks to the aptly-named <code>Batch::Status<\/code> class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;adds jobs to a batch&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> recipe <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> recipe<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetRawVeggiesWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetBaconWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetCupOfSoupWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> batch_status <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Status<\/span><span style=\"color:#f92672\">.<\/span>new(recipe<span style=\"color:#f92672\">.<\/span>bid)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(batch_status)<span style=\"color:#f92672\">.<\/span>to have_attributes(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">total<\/span>: <span style=\"color:#ae81ff\">3<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">pending<\/span>: <span style=\"color:#ae81ff\">3<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> complete?: <span style=\"color:#66d9ef\">false<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>After making our batch and checking on the status, we see there are three jobs, but none of them ran. That is because this is in a test, and we&rsquo;re using the <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Testing#testing-worker-queueing-fake\">Sidekiq fake adapter<\/a> by default.<\/p><p>Let&rsquo;s update our test to run the batch jobs <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Testing#testing-workers-inline\">inline<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;runs the workers in the batch in inline mode&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> recipe <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>inline! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> recipe<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetRawVeggiesWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetBaconWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetCupOfSoupWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> batch_status <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Status<\/span><span style=\"color:#f92672\">.<\/span>new(recipe<span style=\"color:#f92672\">.<\/span>bid)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(batch_status)<span style=\"color:#f92672\">.<\/span>to have_attributes(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">pending<\/span>: <span style=\"color:#ae81ff\">0<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> complete?: <span style=\"color:#66d9ef\">true<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">total<\/span>: <span style=\"color:#ae81ff\">5<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The jobs executed and the batch is complete. Note that the total number of jobs is five, even though we enqueued three jobs. Interesting! Let&rsquo;s leave that aside as we explore what to do now that we have a batch that completes.<\/p><h2 id=\"calling-back-next-steps\">Calling (back) next steps <a href=\"#calling-back-next-steps\">\ud83d\udd17<\/a><\/h2><p>The reason we created a batch was so we could do something with the results of our jobs when they finished. We wanted them to run independently, so we can take advantage of parallel execution. But we need to have the system take action once they&rsquo;re all done.<\/p><p>Sidekiq batches respond to <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Batches#callbacks\">callbacks<\/a>. We&rsquo;ll focus on two of the three available callbacks: complete and success.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ActingLessonCallback<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">on_complete<\/span>(status, options)<\/span><\/span><span style=\"display:flex;\"><span> puts <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>options<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#39;name&#39;<\/span><span style=\"color:#f92672\">]<\/span><span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\"> went to Craft Services&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">on_success<\/span>(status, options)<\/span><\/span><span style=\"display:flex;\"><span> puts <span style=\"color:#e6db74\">&#34;Baby, you&#39;ve got a stew goin&#39;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Each callback method accepts two arguments. One for the status, and another for a set of options. We&rsquo;re using those options in the <code>on_complete<\/code> callback to pass a name to the status message.<\/p><p>Also, I&rsquo;m sorry I misled you earlier. This isn&rsquo;t really about a recipe. It&rsquo;s an <a href=\"https:\/\/www.youtube.com\/watch?v=oDOffqDsV5Q\">acting lesson<\/a>.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/oDOffqDsV5Q?si=dHN-tl96__zJpc_0\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><h2 id=\"getting-an-audition-callback\">Getting an audition callback <a href=\"#getting-an-audition-callback\">\ud83d\udd17<\/a><\/h2><p>Now we know how to use a callback and we created a class to house our callbacks. Let&rsquo;s put it to use by telling our batch about it, and seeing how they get used.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;runs a callback&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> acting_lesson <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> acting_lesson<span style=\"color:#f92672\">.<\/span>on(<span style=\"color:#e6db74\">:complete<\/span>, <span style=\"color:#66d9ef\">ActingLessonCallback<\/span>, <span style=\"color:#e6db74\">&#34;name&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Kevin&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> acting_lesson<span style=\"color:#f92672\">.<\/span>on(<span style=\"color:#e6db74\">:success<\/span>, <span style=\"color:#66d9ef\">ActingLessonCallback<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>inline! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> acting_lesson<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetRawVeggiesWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetBaconWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">GetCupOfSoupWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>to output(<span style=\"color:#e6db74\">&#34;Kevin went to Craft Services<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>\\<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Baby, you&#39;ve got a stew goin&#39;<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_stdout<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We registered our callbacks with the batch using the <code>on<\/code> method.<\/p><p>Remember those two additional jobs in the prior example? Where we enqueued three jobs, but the total count was five? Those extra jobs were these callbacks firing. Even though we didn&rsquo;t register any callbacks, the events still fired.<\/p><p>In this test our batch executed, triggering both callback events. As expected, the callbacks output their message. Completion! Success!<\/p><h2 id=\"a-complete-definition-of-success\">A complete definition of success <a href=\"#a-complete-definition-of-success\">\ud83d\udd17<\/a><\/h2><p>While success and complete may sound similar, they have specific and different meanings. The success callback is perhaps the more obvious one. It triggers when the jobs in the batch have completed successfully.<\/p><p>That means that a batch can complete and not be successful - leading us to the <code>on_complete<\/code> callback. That fires when all the jobs have executed. Some of the jobs could have failed. Some may be in the retry queue. But they have run at least once.<\/p><p>To show this, let&rsquo;s create a few more Sidekiq jobs. We&rsquo;ll join the steps of our acting lesson together in one job. And we&rsquo;ll create one more that always fails.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ActingLessonWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">HugeMistakeWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span> sidekiq_options <span style=\"color:#66d9ef\">retry<\/span>: <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#e6db74\">&#34;I think I&#39;d like my money back&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now we&rsquo;ll run another test, creating a batch with these two jobs. At the end, we&rsquo;ll check the status of the batch.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;completes once each job has run once, regardless of success&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> drama_coach <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>inline! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> drama_coach<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ActingLessonWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">HugeMistakeWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><span style=\"color:#f92672\">.<\/span>to raise_error <span style=\"color:#e6db74\">&#34;I think I&#39;d like my money back&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> batch_status <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Status<\/span><span style=\"color:#f92672\">.<\/span>new(drama_coach<span style=\"color:#f92672\">.<\/span>bid)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(batch_status)<span style=\"color:#f92672\">.<\/span>to have_attributes(<\/span><\/span><span style=\"display:flex;\"><span> complete?: <span style=\"color:#66d9ef\">true<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">total<\/span>: <span style=\"color:#ae81ff\">4<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">pending<\/span>: <span style=\"color:#ae81ff\">1<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">failures<\/span>: <span style=\"color:#ae81ff\">1<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">success_pct<\/span>: <span style=\"color:#ae81ff\">75<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">0<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Even though not all the jobs were successful, the batch still reports itself as complete. We can see the failure, and the job that failed as pending. We can also look at the success percentage of the batch to understand that not all the jobs succeeded.<\/p><h2 id=\"dress-rehearsal-implications\">Dress rehearsal implications <a href=\"#dress-rehearsal-implications\">\ud83d\udd17<\/a><\/h2><p>Something to be mindful of when running your batches in tests is when the callback will fire. To really have some fun, let&rsquo;s do something I don&rsquo;t reach for often: use a global variable.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>$global <span style=\"color:#f92672\">=<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ll create a job that increments the global.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">CounterWorker<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Job<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">perform<\/span><\/span><\/span><span style=\"display:flex;\"><span> $global <span style=\"color:#f92672\">+=<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ll have a callback after the batch succeeds. It outputs how many times the jobs incremented the counter.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">BatchCallback<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">on_success<\/span>(status, options)<\/span><\/span><span style=\"display:flex;\"><span> puts <span style=\"color:#e6db74\">&#34;Jobs run: <\/span><span style=\"color:#e6db74\">#{<\/span>$global<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>In our test, we have a batch that fires the callback and enqueues the counter job twice.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;runs the success callback after the first job is run with inline test mode&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> batch <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>on(<span style=\"color:#e6db74\">:success<\/span>, <span style=\"color:#66d9ef\">BatchCallback<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>inline! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><span style=\"color:#f92672\">.<\/span>to output(<span style=\"color:#e6db74\">&#34;Jobs run: 1<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_stdout<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect($global)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our global says both jobs ran; however, the output from our callback says only one ran. Both are correct - at different points in time! Remember, these jobs in this <em>test<\/em> are performed inline. The first counter job was enqueued and run, incrementing the counter. Then Sidekiq checked to see if any other jobs were in the batch. At this point there aren&rsquo;t, so it triggers the callbacks. The success callback outputs that one job ran.<\/p><p>Then, the second job in the batch was enqueued and run, incrementing the counter. The end of our test verifies that the global variable is currently set to two. The callback is not executed again because it already ran.<\/p><p>If you&rsquo;re running batches in tests, want to test the callback of a batch, and the callback depends on the state or results of all the jobs in the batch, you may end up with a surprising result.<\/p><h2 id=\"bulking-up-mid-rehearsal\">Bulking up mid-rehearsal <a href=\"#bulking-up-mid-rehearsal\">\ud83d\udd17<\/a><\/h2><p>If you really need to test this, you <em>could<\/em> work around this by changing how you enqueue the jobs in your batch. Using <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Bulk-Queueing\">bulk queueing<\/a> will get you the result you expect.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;runs the success callback after all jobs run when pushed in bulk with inline test mode&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> batch <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>on(<span style=\"color:#e6db74\">:success<\/span>, <span style=\"color:#66d9ef\">BatchCallback<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>inline! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>jobs { <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_bulk(<span style=\"color:#f92672\">[[]<\/span>, <span style=\"color:#f92672\">[]]<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><span style=\"color:#f92672\">.<\/span>to output(<span style=\"color:#e6db74\">&#34;Jobs run: 2<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_stdout<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect($global)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This also enqueues two jobs. When run with the inline test adapter, they both run before the batch callbacks fire. So, both the callback and the global are consistent.<\/p><p>I point this out to show you can do this, and also to perhaps introduce bulk queueing to you. However, if your tests rely on this, I would advocate to reconsider your testing strategy. Test the callback in isolation to verify that part of your code. Trust Sidekiq to manage the orchestration of firing the callback for the batch.<\/p><h2 id=\"a-draining-performance\">A draining performance <a href=\"#a-draining-performance\">\ud83d\udd17<\/a><\/h2><p>You may recall that our first test didn&rsquo;t run the jobs because we were using the fake test adapter. I have one other word of caution to point out that I noticed using the fake adapter. Draining the queue that the jobs are enqueued in for a batch later in the test also does not trigger the callbacks.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;doesn&#39;t run the success callback of a batch when draining the queue in fake test mode&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> batch <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>on(<span style=\"color:#e6db74\">:success<\/span>, <span style=\"color:#66d9ef\">BatchCallback<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> batch<span style=\"color:#f92672\">.<\/span>jobs <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>perform_async<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect { <span style=\"color:#66d9ef\">CounterWorker<\/span><span style=\"color:#f92672\">.<\/span>drain }<span style=\"color:#f92672\">.<\/span>not_to output(<span style=\"color:#e6db74\">&#34;Jobs run: 2<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_stdout<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect($global)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Lastly on testing, to have these callbacks fire in tests, you must <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Batches#testing\">add middleware<\/a> to your tests.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>around(<span style=\"color:#e6db74\">:example<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>example<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>server_middleware <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>chain<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> chain<span style=\"color:#f92672\">.<\/span>add <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Server<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> example<span style=\"color:#f92672\">.<\/span>run<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>server_middleware <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>chain<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> chain<span style=\"color:#f92672\">.<\/span>remove <span style=\"color:#66d9ef\">Sidekiq<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Batch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Server<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"curtain-call\">Curtain call <a href=\"#curtain-call\">\ud83d\udd17<\/a><\/h2><p>This has been an introduction to Sidekiq&rsquo;s batch functionality. It <em>also<\/em> provides a valuable life lesson on acting. Batches can be a great option to parallelize work and report the result of, or combine, those pieces. You can also use it to build complex workflows. Dig into batches in more detail on <a href=\"https:\/\/github.com\/sidekiq\/sidekiq\/wiki\/Batches\">Sidekiq&rsquo;s wiki<\/a>.<\/p>"},{"title":"Tracks Not At RailsConf 2024","link":"https:\/\/kevinjmurphy.com\/posts\/tracks-not-at-railsconf-2024\/","pubDate":"Fri, 15 Mar 2024 10:00:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/tracks-not-at-railsconf-2024\/","description":"<h2 id=\"railsconf-2024\">RailsConf 2024 <a href=\"#railsconf-2024\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m on the <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/\">RailsConf 2024 Program Committee<\/a>. We just released the <a href=\"https:\/\/railsconf.org\/schedule\/\">program<\/a> for this year&rsquo;s event, and I hope you&rsquo;ll join us!<\/p><p>One thing we don&rsquo;t have are formally-themed tracks. In past years, our CFP might have included prompts for different topics. Or we&rsquo;d group some talks in publicly-shared and advertised ways.<\/p><p>We aren&rsquo;t doing that this year. That lets us focus on the overall conference theme: building with Rails. However, that didn&rsquo;t stop me from brainstorming a list of possible tracks, in case we did want to add them in.<\/p><p>The following is a list of what could have been, but will not be: tracks that are not part of the RailsConf 2024 program.<\/p><h2 id=\"lend-an-enginear\">Lend an Enginear <a href=\"#lend-an-enginear\">\ud83d\udd17<\/a><\/h2><p>Story Time! Gather round to hear harrowing tales of software development. Was the bug coming from inside the framework? What can we learn from these experiences? Follow the hero&rsquo;s journey of these presenters. Listen to adventures both imagined and too strange for fiction.<\/p><h2 id=\"rails-train-ing\">Rails Train-ing <a href=\"#rails-train-ing\">\ud83d\udd17<\/a><\/h2><p>Unlock new perspectives on using the Rails toolbox. Talks in this track will help you level up on Rails functionality. They&rsquo;ll build up a stronger understanding of framework features.<\/p><h2 id=\"rails-engine-uity\">Rails Engine-uity <a href=\"#rails-engine-uity\">\ud83d\udd17<\/a><\/h2><p>This track highlights people combining Rails with other concepts. This may include a variety of front-end tools. It may show connecting with different data sources. I&rsquo;m sure there are things I can&rsquo;t even imagine that submitters would surprise us with.<\/p><h2 id=\"railway-maintenance\">Railway Maintenance <a href=\"#railway-maintenance\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re all about setting ourselves up for long-haul success here. How can we prevent future pain in managing our applications? How can we get ourselves out of sticky situations? What tools, systems, and mindsets make our apps more amenable to change? How can we otherwise make our apps better in the future?<\/p><h2 id=\"express-rail\">Express Rail <a href=\"#express-rail\">\ud83d\udd17<\/a><\/h2><p>Here is a one-way ticket to building foundational Rails knowledge. Talks in this track assume no prior experience for the audience. This track will bring you up to speed quickly.<\/p><h2 id=\"freight-transport\">Freight Transport <a href=\"#freight-transport\">\ud83d\udd17<\/a><\/h2><p>This is where all the heavy technical talks combine in one long train traveling down the same track. Talks may dig into details on Rails source code. They could explore dreams for the future.<\/p><h2 id=\"passenger-train\">Passenger Train <a href=\"#passenger-train\">\ud83d\udd17<\/a><\/h2><p>The Ruby community is so delightful because of the people in it. These talks highlight that. They provide insight on how to improve our human connection, to code and each other.<\/p><h2 id=\"tunnel-vision\">Tunnel Vision <a href=\"#tunnel-vision\">\ud83d\udd17<\/a><\/h2><p>Talks in this track are deep dives on specific topics. Here is a chance for speakers to get into intense detail. Share the knowledge that it feels like only you know. Combine the wisdom of the community on one area in a tight package.<\/p><h2 id=\"railsconf-2024-program\">RailsConf 2024 Program <a href=\"#railsconf-2024-program\">\ud83d\udd17<\/a><\/h2><p>While those tracks aren&rsquo;t in this year&rsquo;s program, we have lots of <a href=\"https:\/\/railsconf.org\/schedule\/\">amazing sessions<\/a> that are. Get your <a href=\"https:\/\/ti.to\/railsconf\/2024\">ticket<\/a> today!<\/p><p>There may also be some subtle ordering to the schedule. What themes do you see? What sessions are you looking forward to? <a href=\"https:\/\/ruby.social\/@kevin_j_m\/112090218913981735\">Let me know<\/a>!<\/p>"},{"title":"Joining RailsConf 2024 Program Committee","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/","pubDate":"Mon, 05 Feb 2024 17:00:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2024-program-committee\/","description":"<h2 id=\"railsconf-2024-program\">RailsConf 2024 Program <a href=\"#railsconf-2024-program\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m thrilled to be part of the RailsConf 2024 <a href=\"https:\/\/railsconf.org\/about\/\">Program Committee<\/a>. <a href=\"https:\/\/railsconf.org\/\">RailsConf<\/a> is taking place in Detroit, MI from May 7-9. We&rsquo;ll have two days of talks with a hack day\/community day in between.<\/p><p>To put together a great program, we need <strong>your help<\/strong>. I hope you&rsquo;ll consider submitting a talk proposal to our <a href=\"https:\/\/sessionize.com\/railsconf2024\/\">CFP<\/a>, which is open until February 13. I want to read your proposal!<\/p><p>Thank you to Ufuk Kayserilioglu and Andy Croll for inviting me to participate. Special thanks to past Program Committee members for answering my questions.<\/p><p>Whether as a speaker or an attendee, I hope I&rsquo;ll see you in Detroit this May.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/railsconf_2024_program_committee.png\" alt=\"Pictures of the RailsConf 2024 Program Committee Members\"><\/figure>"},{"title":"Slowing Down","link":"https:\/\/kevinjmurphy.com\/posts\/slowing-down\/","pubDate":"Sun, 07 Jan 2024 20:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/slowing-down\/","description":"<h2 id=\"the-plan-going-forward\">The plan going forward <a href=\"#the-plan-going-forward\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve been consistently publishing at least one article a month on the internet for more than three years. I&rsquo;ve since formalized that so that by the third Monday of the month, there&rsquo;d be a new article. That may not sound like much, but it&rsquo;s been what&rsquo;s worked for me.<\/p><p>I&rsquo;m no longer going to hold myself to that content calendar. I&rsquo;m still going to write, but I&rsquo;m going to intentionally break the streak to have something new every month. There will be a new post when I&rsquo;m inspired or incentivized to do so.<\/p><h2 id=\"historical-context\">Historical context <a href=\"#historical-context\">\ud83d\udd17<\/a><\/h2><p>I started writing to kick-start a technical blog of my employer at the time. I did benefit from getting editing and early review from my peers, but wrote all the content off-hours. I <em>enjoyed<\/em> writing, and I enjoyed <em>sharing<\/em> it as well.<\/p><p>Monthly there&rsquo;d be a round-up of posts, videos, documentation, podcasts, or anything else people in the company found helpful. We&rsquo;d write a small blurb and include a link to it. I organized that effort at the time to make sure there was at least one thing a month posted.<\/p><p>I&rsquo;d also write at least one other post a month. Sometimes more. This wasn&rsquo;t tremendously scheduled. Some months I&rsquo;d have two-part posts that I&rsquo;d publish a week apart. Some months I would drop my post whenever it fit in against other authors on the company blog.<\/p><p>When I left that job, I kept writing. I dropped maintaining a personal monthly round-up of neat stuff. I did keep up with original content. Occasionally it was about things that were in air with the larger Ruby community. Mostly it was things that were interesting to me at the time. It may have been something I just encountered for work. It may related to a conference talk I was putting together. But, every month something ended up on <a href=\"https:\/\/kevinjmurphy.com\">kevinjmurphy.com<\/a>.<\/p><p>In July 2022 I started a <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newsletter<\/a>. To me, that cemented that I needed to keep delivering on a consistent schedule. Consistency is a core engagement mechanism to cultivate and maintain an audience. And that&rsquo;s what I&rsquo;ve done, even before the newsletter. Now it&rsquo;d be more obvious (maybe?) if I didn&rsquo;t.<\/p><h2 id=\"trial-period\">Trial period <a href=\"#trial-period\">\ud83d\udd17<\/a><\/h2><p>I intentionally said that I&rsquo;ve been publishing monthly. Lately, I haven&rsquo;t been <strong>writing<\/strong> monthly. I started feeling like I wanted to pull back in 2022. But I had just started the newsletter. I didn&rsquo;t, I couldn&rsquo;t, abandon that. Looking back, I think I started the newsletter in the hopes that it would reinvigorate my writing. I&rsquo;m still very thankful that I have the newsletter. Thanks to everyone reading this who subscribes. But it hasn&rsquo;t gotten me excited to write. It&rsquo;s made it more public (maybe just to me) whenever I got close to missing my self-imposed deadline to write. Which has not been the best environment to write in for fun.<\/p><p>Coincidentally, at the same time I was just about to have a fresh stream of content to publish. I delivered a fresh conference talk at <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/\">RubyConf Mini 2022<\/a>. Not one to let content go to waste, I turn each of my talks into blog posts. Conference talks tend to be more dense in the amount of material, so they usually become a blog post series. This talk was no exception. I had the <a href=\"https:\/\/kevinjmurphy.com\/tags\/anyone-can-play-guitar\/\">posts written<\/a>, along with writing the talk. I had published the <a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">first entry<\/a> before the conference. It was content I had to cut from my talk, so I used it on my blog to promote the upcoming conference. I released <a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">another<\/a> just before the talk, but didn&rsquo;t publicize it until the day of the talk.<\/p><p>That still left three posts ready to go. Previously, I would have published these maybe a week or so apart. But I did something different this time. I sat on them.<\/p><p>Instead, I published my year in review post in December. That followed with an unexpected post to end the year about a <a href=\"https:\/\/kevinjmurphy.com\/posts\/my-first-code-commit-in-ruby\/\">contribution to Ruby<\/a>. Then, because Ruby 3.2 was top of mind at the start of year, I wrote about <a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/\">new 3.2 functionality<\/a> in January.<\/p><p>I didn&rsquo;t get around to continue publishing the Anyone Can Play Guitar series until March. At that time, I had a healthy backlog of articles ready to go. But I held off on publishing them. I intentionally only published one a month.<\/p><p>That gave me breathing room. In March, I knew I had articles written that would last me through August. I didn&rsquo;t stop writing. I stopped writing because I felt I had to. I had a set of posts ready to go to fall back on. When I&rsquo;d write a new article, I&rsquo;d decide if I should publish it more immediately, or move it back in line behind the others. For example, I&rsquo;m writing <em>this post<\/em> in August, 2023. I know it&rsquo;s not getting published for a while though. I&rsquo;d shift things around. Is there a big conference coming up? I&rsquo;d publish posts I had ready to go about preparing for, or going to, conferences. Is there something that I&rsquo;m really excited that I just wrote? I&rsquo;d move it to the front of the line.<\/p><p>I kept the schedule, but I didn&rsquo;t write to the schedule.<\/p><h2 id=\"settling-in\">Settling in <a href=\"#settling-in\">\ud83d\udd17<\/a><\/h2><p>I prefer this writing arrangement. I put something together when I have the time, or when I&rsquo;m particularly motivated. I&rsquo;m not out of ideas. Much like my list of conference talk ideas, I have pages of ideas for blog posts unwritten. And new ideas spring to life, taking priority over those other ideas. When I feel like writing <em>something<\/em>, but don&rsquo;t know what, I scroll through my list. I see what catches my fancy - or what generates a different idea.<\/p><h2 id=\"consequences\">Consequences <a href=\"#consequences\">\ud83d\udd17<\/a><\/h2><p>I still have some posts already written that I&rsquo;ve been sitting on. I&rsquo;ll probably space them out to publish a bit further than every month. If something comes up to fill the void, great - I&rsquo;ll hit publish on it. Otherwise, I&rsquo;ll wait for the time to drop the written post.<\/p><p>And when those run out? I&rsquo;ll write when I feel like it.<\/p><p>In terms of building a brand, or an audience, I&rsquo;m led to believe this is death. Consistency and guarantees help breed expectation and comfort. I&rsquo;m breaking that guarantee to take the expectation off of me.<\/p><p>I&rsquo;ve never published something that I thought was bad because I thought I had to. But I&rsquo;ve definitely written under a time schedule that was entirely my own doing. It&rsquo;s less fun to do it when you have to.<\/p><p>And that&rsquo;s why I do this - for fun. Sure, I do it because I hope it helps someone. Mostly I do it because it helps me. My audience primarily is me. Don&rsquo;t get me wrong, I&rsquo;m very happy that other people read this. But, I start writing because I want to get ideas out of my head, and\/or because I want to be able to reference it in the future.<\/p><p>That&rsquo;s why I make references or puns that maybe only I get. That&rsquo;s why some of my technical posts are excuses to accompany a silly title I thought of. I don&rsquo;t share drafts with others prior to publishing. Partly that&rsquo;s because I don&rsquo;t want to burden someone else with that responsibility. But also, my approach has been when it&rsquo;s good enough for me, it&rsquo;s good enough to publish.<\/p><p>There have certainly been second-order benefits of writing (or giving <a href=\"https:\/\/kevinjmurphy.com\/speaking\/\">conference talks<\/a>, or going on <a href=\"https:\/\/kevinjmurphy.com\/speaking\/#podcasts\">podcasts<\/a>). I&rsquo;ve met people and made friends I wouldn&rsquo;t have otherwise. My increased visibility made it more helpful when I had a <a href=\"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/\">public job search<\/a> in 2022. It&rsquo;s a huge thrill when I see an article end up <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">featured<\/a> elsewhere. It&rsquo;s one of the most meaningful career experiences I&rsquo;ve had hearing how a post or talk helped someone. It&rsquo;s a delight finding out that someone liked what I wrote. Or even simply that someone read it.<\/p><p>But, for now, I&rsquo;m coming out of the content mines. I&rsquo;ll keep delivering posts, but not with as much rigor. To keep the analogy going, I&rsquo;ll instead visit alluvial deposits to sift for gems to publish on occasion. That&rsquo;s one of those references that is just for me. This one is from one of my <a href=\"https:\/\/kevinjmurphy.com\/posts\/browser-history\/\">talks<\/a>.<\/p><h2 id=\"what-it-means-for-you\">What it means for you <a href=\"#what-it-means-for-you\">\ud83d\udd17<\/a><\/h2><p>Probably not much? If you subscribe to my <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newsletter<\/a>, you&rsquo;ll keep getting posts in your inbox when they&rsquo;re written. If you follow me on <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">social media<\/a>, you&rsquo;ll keep getting posts in your feed when they&rsquo;re written (unless any algorithm says otherwise). If you follow this site via <a href=\"https:\/\/kevinjmurphy.com\/index.xml\">RSS<\/a>, thanks for keeping RSS alive - and you&rsquo;ll keep getting posts in your feed when they&rsquo;re written. If you come to this site when my posts are featured in other publications, you&rsquo;ll never read this, but you&rsquo;ll keep seeing them when they&rsquo;re featured, if you click on them.<\/p><p>You just won&rsquo;t see them as often. At least that&rsquo;s the plan. Who knows - maybe this will make me realize I should actually push something weekly. Just kidding, I could barely make monthly work. Maybe I&rsquo;ll have more time to catch up with you directly. Drop me a line however we&rsquo;re connected. I&rsquo;m happy to hear from you.<\/p><h2 id=\"what-it-means-for-me\">What it means for me <a href=\"#what-it-means-for-me\">\ud83d\udd17<\/a><\/h2><p>This frees me from my self-imposed deadlines and anxiety about publishing something. It gives me a chance to dig into something that may be a little meatier, but may not be ready in time. It gives me the license to just&hellip;not write something. I&rsquo;m not entirely sure what it means for me. But I think it&rsquo;ll be good for me (otherwise, why do it?). And if it&rsquo;s not? We can all have a laugh about that time I made this self-aggrandizing pronouncement. Like someone slinking back to social media after announcing their exit. Hey, I&rsquo;m sure it felt right to them at the time. This feels right to me now.<\/p><p>Though I&rsquo;ve planned this for a while, announcing it now makes it public when I know I&rsquo;ll have to step away from a few things to handle some unexpected family business. So, thanks past Kevin for writing this! It&rsquo;s all ready to go, and all I had to do was add this paragraph and publish it.<\/p><h2 id=\"what-slow-down\">What slow down? <a href=\"#what-slow-down\">\ud83d\udd17<\/a><\/h2><p>So, there you go. Over 1,500 words about how I&rsquo;m going to write words less often. Or, at least, feel forced to publish them less often. The irony is not lost on me. If you read this far, let me know. Clearly, we should catch up.<\/p>"},{"title":"Blue Ridge Ruby Coverage Talk Video","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-coverage-video\/","pubDate":"Wed, 03 Jan 2024 08:00:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-coverage-video\/","description":"<h2 id=\"blue-ridge-ruby-videos\">Blue Ridge Ruby Videos <a href=\"#blue-ridge-ruby-videos\">\ud83d\udd17<\/a><\/h2><p>I had a great time at <a href=\"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023-recap\/\">Blue Ridge Ruby<\/a> last year. This was the first time I shared &ldquo;<a href=\"https:\/\/kevinjmurphy.com\/posts\/coverage\/\">Enough Coverage To Beat the Band<\/a>&rdquo; with an in-person audience. The first stop on my &ldquo;Ruby&rsquo;s Got You Covered&rdquo; World Tour that didn&rsquo;t stream from the office in my house.<\/p><p>You can now view <a href=\"https:\/\/www.youtube.com\/@BlueRidgeRuby\/videos\">all the talks<\/a> from the conference on YouTube! That includes <a href=\"https:\/\/youtu.be\/H4pBjWOMr_Y\">mine<\/a>, which started off the events on day 1. Because I went first, they were still ironing out some kinks, like with the audio, but I hope you enjoy it nonetheless.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/H4pBjWOMr_Y?si=LuO1nsqTJRCMOOIk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe>"},{"title":"2023 Year-End Review","link":"https:\/\/kevinjmurphy.com\/posts\/2023-review\/","pubDate":"Fri, 15 Dec 2023 20:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/2023-review\/","description":"<p>This isn&rsquo;t a &ldquo;real&rdquo; post. This is a summary of all the things that made up my year in 2023 (almost all in a professional context). Thanks to all who were a part of it.<\/p><h2 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h2><p>I published 11 articles about Ruby or software development in general this year. I started with changes to Ruby&rsquo;s <a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/\">Coverage module<\/a> in 3.2. My last post of the year covered what I saw at <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2023-recap\/\">RubyConf<\/a>.<\/p><p>In between, I continued my <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Anyone Can Play Guitar Series<\/a> and my <a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">Conference Talk Preparation Series<\/a>. I also published one-off articles about modifying <a href=\"https:\/\/kevinjmurphy.com\/posts\/modify-frozen-hash\/\">frozen hashes<\/a>, <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-inherited-behavior\/\">testing inherited behavior<\/a>, and more.<\/p><p>I continue sending my posts to <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newslettersubscribers<\/a>. You can still subscribe via <a href=\"https:\/\/kevinjmurphy.com\/index.xml\">RSS<\/a> or try to keep up wherever I post on <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">social media<\/a>.<\/p><p>My most-read posts are those that end up in <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">other newsletters<\/a>. This year, my writing appeared in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> 6 times, <a href=\"https:\/\/rubyradar.dev\/\">Ruby Radar<\/a> 3 times, <a href=\"https:\/\/newsletter.shortruby.com\/\">Short Ruby Newsletter<\/a> 8 times, and <a href=\"https:\/\/tinyletter.com\/developeravocados\/\">Developer Avacados Weekly<\/a> once.<\/p><p>I intentionally cut down my publishing to once a month. That was always my minimum, but this year I forced myself to sit on anything I had written so I had a backlog. For example, the <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Anyone Can Play Guitar Series<\/a> was written along with my RubyConf Mini 2022 talk. However, I held off on publishing it all at once. Instead, I dripped an article out each month.<\/p><p>That gave me freedom to write something new if I wanted, but if not, still have something to share every month. I liked not having the (self-imposed) monthly deadline to write. I&rsquo;ll have more on that soon as I think about 2024.<\/p><h2 id=\"speaking\">Speaking <a href=\"#speaking\">\ud83d\udd17<\/a><\/h2><p>I made it the entire year without submitting a single CFP. However, I did present one talk. I spoke at <a href=\"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023-recap\/\">Blue Ridge Ruby<\/a>, which was great. The &ldquo;Ruby&rsquo;s Got You Covered&rdquo; tour finally had an in-person date.<\/p><p>That didn&rsquo;t stop me from finding other ways to speak this year. I run an internal knowledge sharing session at work, which went through some iteration this year and where I presented regularly.<\/p><p>I helped conference speakers more this year. I was a coach for the RubyCentral CFP coaching sessions for both RailsConf and RubyConf. I was a speaker mentor at RubyConf for the first time. I helped friends prepare their proposals and talks when asked.<\/p><p>I also podcasted more this year. I was a guest on <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-appearance\/\">The Ruby on Rails Podcast<\/a>. Then a co-host on <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-cohost\/\">The Ruby on Rails Podcast<\/a>. Lastly, I joined <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2023\/\">Ruby for All<\/a>.<\/p><p>If you have an event you&rsquo;d like me to speak at, or a podcast you&rsquo;d like me to guest on, let me know!<\/p><h2 id=\"reading\">Reading <a href=\"#reading\">\ud83d\udd17<\/a><\/h2><p>I was a technical reviewer for Noel Rappin and The Pragmatic Bookshelf again. Noel is bringing back the &ldquo;Pickaxe book&rdquo;. I reviewed <a href=\"https:\/\/pragprog.com\/titles\/ruby5\/programming-ruby-3-3-5th-edition\/\">Programming Ruby 3.3<\/a> this year. I can&rsquo;t wait to see the final version.<\/p><p>I also reviewed Andrew Atkinson&rsquo;s <a href=\"https:\/\/pragprog.com\/titles\/aapsql\/high-performance-postgresql-for-rails\/\">High Performance PostgreSQL for Rails<\/a>. It&rsquo;s a helpful companion into the depths of Postgres functionality. If you&rsquo;re looking to step out of the comfort of a Rails console and into a psql console, I recommend taking this with you.<\/p><h2 id=\"employer\">Employer <a href=\"#employer\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve been at <a href=\"https:\/\/www.pubmark.com\/\">Pubmark<\/a> all year. I enjoy the work that I do, and the people I do it with. I&rsquo;ve been comfortable in my work this year, which has been really helpful in my personal life. Maybe I&rsquo;ll try to intentionally be a little less comfortable next year.<\/p><h2 id=\"new-to-me-in-2023\">New (to me) in 2023 <a href=\"#new-to-me-in-2023\">\ud83d\udd17<\/a><\/h2><p>Here&rsquo;s where I take a break to step out of work and talk about things (mostly music) I got into this year.<\/p><h3 id=\"history-books\">History Books <a href=\"#history-books\">\ud83d\udd17<\/a><\/h3><p>The Gaslight Anthem got back together and released a new record this year, featuring a song with the one and only Bruce Springsteen. History Books as an album is a great fall-time listen with its moody textures.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/mXs1n2bL0oE?si=Dds7Lr1y_c3KFOEO\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><h3 id=\"strong-female-character\">Strong Female Character <a href=\"#strong-female-character\">\ud83d\udd17<\/a><\/h3><p>Maybe you know Fern Brady from <a href=\"https:\/\/kevinjmurphy.com\/posts\/2021-review\/#taskmaster\">Taskmaster<\/a>. Maybe you don&rsquo;t (and if you don&rsquo;t, you should). Her <a href=\"https:\/\/www.bookbub.com\/books\/strong-female-character-by-fern-brady-2023-04-24\">memoir<\/a> is the most impactful book I read this year.<\/p><h3 id=\"jared-james-nichols\">Jared James Nichols <a href=\"#jared-james-nichols\">\ud83d\udd17<\/a><\/h3><p>Driving riffs, dynamic leads, and a simple guitar rig. I was first introduced to Jared this year, and I&rsquo;m better for it.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/zNE4P_sW_Gw?si=l8pQqmI-qzjGq8nv\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><h3 id=\"electric-cargo-bike\">Electric Cargo Bike <a href=\"#electric-cargo-bike\">\ud83d\udd17<\/a><\/h3><p>I joined the e-bike crew with my <a href=\"https:\/\/bennobikes.com\/e-bikes\/boost\/\">Benno Boost<\/a> this year. It took a few rides to get used to the pedal assist, but now it feels natural. My daughter loves hopping in back and riding to the playground, library, or wherever else. Every morning, we take the bike to drop her off at kindergarten.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/benno_boost.jpg\" alt=\"My Benno Boost Cargo Bike\"><\/figure><h3 id=\"toucher-and-rich-no-more\">Toucher and Rich (no more) <a href=\"#toucher-and-rich-no-more\">\ud83d\udd17<\/a><\/h3><p>Hey guys, hey! This one is a downer, but for the first time since 2006, I don&rsquo;t have the <a href=\"https:\/\/985thesportshub.com\/shows\/toucher-rich\/\">Toucher and Rich<\/a> radio program to listen to. I was a daily listener. I&rsquo;m not surprised, but still disappointed, that the show split up. It ending without the chance for a proper good-bye was particularly jarring. I&rsquo;m hoping for good things for everyone previously involved with the show.<\/p><h2 id=\"thank-you\">Thank You <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>Thanks for reading, watching, or listening. I hope your 2024 is better than 2023.<\/p>"},{"title":"Site Search Now Available","link":"https:\/\/kevinjmurphy.com\/posts\/search-functionality\/","pubDate":"Sun, 10 Dec 2023 07:30:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/search-functionality\/","description":"<p>I&rsquo;m no stranger to <a href=\"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/\">search<\/a>. Give me <a href=\"https:\/\/kevinjmurphy.com\/posts\/browser-history\/\">25 minutes<\/a> and I&rsquo;ll tell you my thoughts on how and why we search for things. All that said, one thing you couldn&rsquo;t do on this site for a long time was search. I&rsquo;m proud to announce on behalf of the entire team responsible for this site (me) that an innovative new feature is now available: <a href=\"https:\/\/kevinjmurphy.com\/search\/\">search<\/a>.<\/p><p>Any questions?<\/p><h2 id=\"what-took-so-long\">What took so long? <a href=\"#what-took-so-long\">\ud83d\udd17<\/a><\/h2><p>Two main reasons: self-esteem and laziness. I don&rsquo;t think anyone else will use this functionality. I don&rsquo;t think people will read <em>this<\/em>. Many times, I write for myself. So I want to reference my own posts on occasion. And for myself, I know what I&rsquo;m looking for and when I published it in comparison to other posts. So I&rsquo;ve just been paginating through my results until I find the post.<\/p><p>The other piece is that I don&rsquo;t want to spend a lot of time working on my website design\/layout\/features. That&rsquo;s time I could be writing for my website, or doing literally anything else. My site is intentionally in Hugo, because while I&rsquo;ve written Golang, I&rsquo;m not apt to change code in there. The templating engine is easy enough to do what I want, but I&rsquo;ve long resisted the urge to revamp the theme I chose when starting the site.<\/p><h2 id=\"why-now\">Why now? <a href=\"#why-now\">\ud83d\udd17<\/a><\/h2><p>I was inspired by Justin Searls, who <a href=\"https:\/\/justin.searls.co\/links\/2023-11-29-shout-out-to-pagefind-static-search\/\">recently wrote<\/a> about adding search to his site. If Justin is actually trumpeting some tech and not sharing cursed bugs with it, then it&rsquo;s worth my attention.<\/p><p>And as a side-benefit, maybe I wouldn&rsquo;t have to page through my own site as often.<\/p><h2 id=\"how-did-the-implementation-process-go\">How did the implementation process go? <a href=\"#how-did-the-implementation-process-go\">\ud83d\udd17<\/a><\/h2><p>Just like Justin, I&rsquo;m using <a href=\"https:\/\/pagefind.app\/\">Pagefind<\/a>. The process of implementing the index was really straightforward. I followed their <a href=\"https:\/\/pagefind.app\/docs\/\">Quick Start guide<\/a> and the indexing was done.<\/p><p>With a one-line change to my deploy script, I was set to have this indexing process update for each change to my site. Great work Pagefind!<\/p><p>The part that took time was my inability to know how my own site works and how to make it look nice. My theme has a light and dark mode. After adding Pagefind, I had to wrap up the initial implementation by turning off dark mode, otherwise the Pagefind results were illegible. This felt like a fine trade-off on a Friday night, as I imagine it would only affect me - and I was going to sleep.<\/p><p>The Pagefind docs clearly suggest how to <a href=\"https:\/\/pagefind.app\/docs\/ui-usage\/#customising-the-styles\">change the Pagefind styles<\/a>, even including recommendations for a dark mode. Well done, Pagefind!<\/p><p>The trouble was I needed to understand how my site even implements dark mode. Through this, I learned about CSS&rsquo;s <code>prefers-color-scheme<\/code> <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@media\/prefers-color-scheme\">feature<\/a>. I told you, I don&rsquo;t really want to know implementation details in my site so I&rsquo;m not tempted to change them. I also revisited CSS <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Using_CSS_custom_properties\">custom properties<\/a> and styling input <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/::placeholder\">placeholder values<\/a>.<\/p><p>Did you know Firefox&rsquo;s inspector has the ability to <a href=\"https:\/\/www.linuxadictos.com\/en\/firefox-87-adds-an-option-to-its-inspector-that-allows-us-to-switch-between-light-and-dark-mode-if-the-web-allows-it.html\">toggle light mode and dark mode<\/a> for a site? I didn&rsquo;t, but now I do!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/firefox_inspector_light_dark.png\" alt=\"The Firefox inspector with arrows pointing to how to change to light and dark mode\"><\/figure><h2 id=\"how-do-you-feel-now\">How do you feel now? <a href=\"#how-do-you-feel-now\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m satisfied. I can <a href=\"https:\/\/kevinjmurphy.com\/search\/\">search<\/a> my site. All with very little effort on my behalf. Again, thanks Pagefind! And Justin for the suggestion. I could dig into <a href=\"https:\/\/pagefind.app\/docs\/weighting\/\">modifying<\/a> my index, or <a href=\"https:\/\/pagefind.app\/docs\/sorts\/\">sorting<\/a> results, but I&rsquo;m going to stick with the default experience for now. Those incremental improvements wouldn&rsquo;t have the value add compared to what this already provides. Before there was no search, and now there is!<\/p><p>Is it my greatest work? Something I would put into production at work? Not the way I wrote it. This is not a reflection on Pagefind. I <em>would<\/em> recommend that for a static site. However, I&rsquo;ve committed some <a href=\"https:\/\/github.com\/kevin-j-m\/kjm-blog\/commit\/a36f1cbcc6b665ce69c281addd8e31a90fb56b50\">terrible atrocities<\/a> to HTML and CSS to fit it into my site&rsquo;s theme.<\/p><p>But, I did that styling while <a href=\"https:\/\/www.netflix.com\/TITLE\/81128389\">Emily&rsquo;s Wonder Lab<\/a> was playing on TV. And they were making a tornado. So I wanted to wrap this up and watch it with my daughter. So, it&rsquo;s good enough for me, for now. I hope you enjoy it too.<\/p><p>Happy <a href=\"https:\/\/kevinjmurphy.com\/search\/\">searching<\/a>!<\/p>"},{"title":"RubyConf 2023 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2023-recap\/","pubDate":"Fri, 17 Nov 2023 21:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2023-recap\/","description":"<h2 id=\"rubyconf-2023\">RubyConf 2023 <a href=\"#rubyconf-2023\">\ud83d\udd17<\/a><\/h2><p>RubyConf recently wrapped up in San Diego, California. This post is meant to highlight the great work from all involved. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you once they are available. Unfortunately, I couldn&rsquo;t be everywhere, so this covers what I saw.<\/p><h2 id=\"preparing\">Preparing <a href=\"#preparing\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;d prefer, you can <a href=\"#community-day\">skip<\/a> right to my commentary on the start of the conference.<\/p><p>This is a weird brag, but this is the first conference I&rsquo;ve attended in a long time as an attendee. It&rsquo;s been a while since I haven&rsquo;t been a speaker or on the speaker waiting list. Being a wait-listed speaker means you prep and have a talk ready. You only get to give it if you need to substitute for another speaker on short notice.<\/p><p>This left me with a lot more free time during the conference. More importantly, I had more time before the conference. I still had the &ldquo;conference season&rdquo; itch though. So I channeled that energy in a few different directions.<\/p><h3 id=\"cfp-coaching\">CFP Coaching <a href=\"#cfp-coaching\">\ud83d\udd17<\/a><\/h3><p>First, just as I did for RailsConf, I joined Ruby Central members for the CFP coaching sessions. We helped others with their proposals in small groups. This was a great opportunity to meet with prospective speakers and workshop ideas to submit for the conference. I&rsquo;m glad that Ruby Central has started organizing these events. I hope they continue.<\/p><h3 id=\"podcasting-about-rubyconf\">Podcasting about RubyConf <a href=\"#podcasting-about-rubyconf\">\ud83d\udd17<\/a><\/h3><p>After the CFP process wound down, I joined Brittany Martin as a temporary <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-cohost\/\">co-host<\/a> of The Ruby On Rails podcast. We talked with Allison McMillan and Chelsea Kaufman about the plan for RubyConf 2023. I hope that it helped get people excited about the conference. Even better if it helped convince someone to come after listening.<\/p><p>I also joined Julie and Andrew on the <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2023\/\">Ruby for All<\/a> podcast. The discussion centered around conference speaking.<\/p><h3 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h3><p>I expanded on my blog post from last year where I shared <a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/\">past proposals<\/a>. I turned it into a series on preparing for conferences. I wrote about <a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">my process<\/a> for turning an accepted proposal into a talk. I followed that up with <a href=\"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/\">how I practice<\/a> and prepare for the stage.<\/p><h3 id=\"helping-speakers\">Helping Speakers <a href=\"#helping-speakers\">\ud83d\udd17<\/a><\/h3><p>Lastly, I had more time to help friends who were speaking with their talk preparation. Notably, I was a speaker mentor for the first time. I&rsquo;ve been hesitant to do it in the past when I&rsquo;ve had a talk of my own to prepare. I was really happy to have the time to devote to this. I paired with <a href=\"https:\/\/ruby.social\/@paulreece_\">Paul Reece<\/a>. Paul gave a great talk about <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/530118\">Ruby Polars<\/a>. Paul was very prepared and definitely made the process easy for me. It was super fun to see the evolution of the talk over time from an outsider&rsquo;s perspective.<\/p><h2 id=\"community-day\">Community Day <a href=\"#community-day\">\ud83d\udd17<\/a><\/h2><p>Even after talking about this on <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-cohost\/\">The Ruby on Rails podcast<\/a>, I&rsquo;ll admit that I was still pretty unsure how this would all go. I didn&rsquo;t attend any of the workshops as I wanted to see the implementation of the hack space events.<\/p><p>After an introduction from Allison McMillan and Chelsea Kaufman, the group separated into workshops and the hack space. I spent my day in the hack space. In the morning I spent time at the Hanami table. I&rsquo;ll be honest, I didn&rsquo;t do much of any Hanami work, but I had a great conversation there.<\/p><p>After lunch, I parked at the Ruby LSP table. I worked with <a href=\"https:\/\/codewithjulie.com\/\">Julie<\/a> and <a href=\"https:\/\/www.drbragg.dev\/\">Drew<\/a> on <a href=\"https:\/\/github.com\/Shopify\/ruby-lsp\/pull\/1181\">HEREDOC endings<\/a>. We also found an <a href=\"https:\/\/github.com\/Shopify\/ruby-lsp\/issues\/1182\">issue<\/a> that we spent some time digging into, but ran out of time before we had a solution. It was fun spelunking through the code with friends. <a href=\"https:\/\/ufuk.dev\/\">Ufuk<\/a> joined us in looking at the cursor indentation issue. His sage wisdom helped us navigate through an unfamiliar codebase.<\/p><p>I enjoyed my time at Community Day. For me, I&rsquo;m not sure if it&rsquo;s worth removing a full day of talks. I&rsquo;m a person that really enjoys talks at conferences. I&rsquo;m a person who will gladly join talks in person. But, I have a difficult time committing to watching recorded talks after. I&rsquo;m also biased, because I&rsquo;m a potential speaker (I did not submit to RubyConf this year). One fewer day and fewer tracks means less opportunity for people to have an accepted talk.<\/p><p>That said, I talked to a handful of people who organized sessions after. And the vibe I got from them was that it was a positive time. They were happy to work with the community directly, in-person, on their project. They liked meeting new people interested in helping and giving them a starting push. They enjoyed the experience and what it meant for their project.<\/p><p>I&rsquo;m not sure what the right mix is here. At the least, I think moving Community Day to later in the conference makes sense. That way the organizers can communicate in person with attendees on expectations. Any talks about subjects that will be at Community Day can turn into a call to action to join the hack space.<\/p><p>Could there be a hack space throughout the conference that people could join? That would have a similar cost that workshops have in the past, where people need to miss other sessions to join. It would mean having another room at the conference that Ruby Central needs to rent. Maybe having it spread out throughout the conference would mean a smaller room? It would be a bit like the hallway track, but structured towards particular goals.<\/p><p>Like I said, unfortunately, I have no solid answers here. And I recognize that the way I enjoy conferences is different from many others. I heard resounding praise for Community Day at the conference!<\/p><p>I do like that Ruby Central is responding to feedback and trying out new experiences. I&rsquo;m interested to hear a retrospective from them. How do they feel about Community Day and what form(s) it might take going forward?<\/p><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><h3 id=\"opening-keynote\">Opening Keynote <a href=\"#opening-keynote\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/560574\">Yukihiro &ldquo;Matz&rdquo; Matsumoto<\/a> started the conference after the opening remarks. This was a pre-recorded message. He talked about learning from the history of language design and evolution, to inform the future of Ruby.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_matz.jpg\" alt=\"Matz at RubyConf 2023\"><\/figure><h3 id=\"which-time-is-it\">Which Time Is It? <a href=\"#which-time-is-it\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/531537\">Jo\u00ebl Quenneville<\/a> started the talk sessions. He provided a distinction between looking at a moment in time versus a duration of time. He cautions to think through what types of time can interact with each other (the way that you want or expect). Consider the operator you&rsquo;re using and what it means. Catch the full session to learn more about why you can&rsquo;t add two time instances together.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_joel_stage.jpg\" alt=\"Jo\u00ebl Quenneville at RubyConf 2023\"><\/figure><h3 id=\"get-your-data-prod-ready-fast-with-ruby-polars\">Get your Data prod ready, Fast, with Ruby Polars! <a href=\"#get-your-data-prod-ready-fast-with-ruby-polars\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/530118\">Paul Reece<\/a> gave an introduction to the Polars library. This started with an introduction to the Series and Data Frame data structures. Paul then walked through data cleaning operations available in Polars. He shared a <a href=\"https:\/\/github.com\/paulreece\/polars_resources\">repository<\/a> of resources and code samples at the end of his session.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_paul_stage.jpg\" alt=\"Paul Reece at RubyConf 2023\"><\/figure><h3 id=\"ruby-content-creators\">Ruby Content Creators <a href=\"#ruby-content-creators\">\ud83d\udd17<\/a><\/h3><p>During Community Day, Peter Cai asked me to participate in this <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/186fce40-218a-4d5b-a3f5-01d133b1f7c5\">open space session<\/a>. I sat at the written content table with other bloggers and authors. There were other tables for podcasts, live streaming, and pre-recorded videos. The written content table talked through our processes. We lamented over the difficulties in getting feedback. We shared how to build sustainable writing habits.<\/p><h3 id=\"the-future-of-understanding-ruby-code\">The Future of Understanding Ruby Code <a href=\"#the-future-of-understanding-ruby-code\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/531482\">Kevin Newton<\/a> talked about <a href=\"https:\/\/github.com\/ruby\/prism\">Prism<\/a>, a new Ruby parser. He discussed the challenges that come with parsing Ruby. He shared what&rsquo;s next, and what we can and should expect from our Ruby tooling in the future. He ended with an impassioned discussion about building a contributor community around a single tool.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_kevin_newton.jpg\" alt=\"Kevin Newton at RubyConf 2023\"><\/figure><h3 id=\"the-unbreakable-code-whose-breaking-won-wwii\">The Unbreakable Code Whose Breaking Won WWII <a href=\"#the-unbreakable-code-whose-breaking-won-wwii\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/529111\">Aji Slater<\/a> took us back to Bletchley Park to model an Enigma machine with object-oriented principles. This session features monumentally brilliant illustrations and fantastic storytelling.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_aji.jpg\" alt=\"Aji Slater at RubyConf 2023\"><\/figure><h3 id=\"closing-keynote\">Closing Keynote <a href=\"#closing-keynote\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/560579\">Sharon Steed<\/a> ended the first day of talks with a keynote on actionable empathy. We heard about its relationship with vulnerability and how trust is the bedrock to empathy.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_sharon.jpg\" alt=\"Sharon Steed at RubyConf 2023\"><\/figure><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><h3 id=\"opening-keynote-1\">Opening Keynote <a href=\"#opening-keynote-1\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/560580\">Saron Yitbarek<\/a> opened the last day of RubyConf discussing our superpower. Loving what we do is an underrated source of competitive advantage for us to wield.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_saron.jpg\" alt=\"Saron Yitbarek at RubyConf 2023\"><\/figure><h3 id=\"meet-the-pragmatic-bookshelf\">Meet the Pragmatic Bookshelf <a href=\"#meet-the-pragmatic-bookshelf\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/560583\">Noel Rappin<\/a> joined Dave Thomas and Dave Copeland for a panel discussion on writing. This particularly focused on books with Pragmatic Programmers. We heard about the entire process, from proposal to publication.<\/p><p>One note I found interesting was how the voice of their books changes over time. It intentionally starts with an authoritative tone, telling the reader what to do. As the book progresses and the reader is more familiar with the domain, the tone shifts. Now the tone is more collaborative. The author consults with the reader and uses &ldquo;we&rdquo; more.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_prag_prog.jpg\" alt=\"Noel Rappin, Dave Thomas, and Dave Copeland at RubyConf 2023\"><\/figure><h3 id=\"the-secret-ingredient-how-to-understand-and-resolve-just-about-any-flaky-test\">The Secret Ingredient: How To Understand and Resolve Just About Any Flaky Test <a href=\"#the-secret-ingredient-how-to-understand-and-resolve-just-about-any-flaky-test\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/527141\">Alan Ridlehoover<\/a> walked through a progression of steps to resolve tests failing due to non-determinism, order dependence, and race conditions. With an eye to keeping specs to have one, and only one, reason to exist, he proposes tools and methodologies to trace down these test failures.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_alan.jpg\" alt=\"Alan Ridlehoover at RubyConf 2023\"><\/figure><h3 id=\"livin-la-vida-hanami\">Livin\u2019 La Vida Hanami <a href=\"#livin-la-vida-hanami\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/518035\">Tim Riley<\/a> introduced Hanami as the everything framework. The framework is not our app, but enables our application logic through its layers. While it has support for web essentials, you can choose to not include them and still benefit from the underlying framework benefits.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_tim_stage.jpg\" alt=\"Tim Riley at RubyConf 2023\"><\/figure><h3 id=\"open-source-ruby\">Open Source Ruby <a href=\"#open-source-ruby\">\ud83d\udd17<\/a><\/h3><p>In this <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/decd439d-f9ec-45be-b016-49ea2f99cd3c\">open space session<\/a> I sat at the Sidekiq table. Mike Perham held an informal chat. We discussed open source sustainability and succession planning. There were tables representing other projects, but I&rsquo;m not sure what they covered.<\/p><h3 id=\"closing\">Closing <a href=\"#closing\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/3298cf7c-e3ad-4ade-bb53-61ae24e97b2c\">Adarsh Pandit<\/a> wrapped up RubyConf. We learned that RubyConf 2024 will be in Chicago in the fall. That&rsquo;s following a visit to Detroit in the spring for RailsConf.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf2023_adarsh.jpg\" alt=\"Adarsh Pandit at RubyConf 2023\"><\/figure><h2 id=\"appreciation\">Appreciation <a href=\"#appreciation\">\ud83d\udd17<\/a><\/h2><p>Thank you to my employer, <a href=\"https:\/\/www.pubmark.com\/\">Pubmark<\/a>, for supporting my attendance this year.<\/p><p>Thank you <a href=\"https:\/\/rubycentral.org\">Ruby Central<\/a> for your work to organize these events and supporting the Ruby community all year round.<\/p><p>Thank you to the Program Committee, Scholarship Committee, volunteers, and venue staff for bringing this conference to life.<\/p><p>Thank you sponsors for supporting the Ruby community and making it possible for us to come together.<\/p><p>Thank you Jonan Scheffler and Marty Haught for your years of service and dedication on the Ruby Central board.<\/p><p>Thanks to all the <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-friends-rubyconf-2023\/\">#RubyFriends<\/a>, old and new, that I met in San Diego.<\/p><p>Thank you for reading to the end. Maybe we met at RubyConf 2023. Maybe we didn&rsquo;t. I hope to see you in 2024. I hear Chicago is lovely in November.<\/p>"},{"title":"Ruby Friends at RubyConf 2023","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-friends-rubyconf-2023\/","pubDate":"Wed, 15 Nov 2023 19:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-friends-rubyconf-2023\/","description":"<h2 id=\"ruby-friends-at-rubyconf-2023\">Ruby Friends at RubyConf 2023 <a href=\"#ruby-friends-at-rubyconf-2023\">\ud83d\udd17<\/a><\/h2><p>RubyConf 2023 just wrapped up. It was a pleasure meeting everyone and learning from you all. I took far too few photos, and mostly remembered to take pictures with people on the last day. With no further ado, here are the #RubyFriends pictures I took in San Diego.<\/p><p>In any pictures that feature more than one other person, I&rsquo;ll list them from left to right as they appear in the photo. Also, I&rsquo;m terrible at taking pictures. Sorry everyone.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_Joel.jpg\" alt=\"Jo\u00ebl Quenneville and me at RubyConf 2023\"><\/figure><p>Jo\u00ebl Quenneville gave a <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/531537\">great talk<\/a> right after the keynote to start the conference talk sessions. It&rsquo;s so great to see him. More often than not we have to travel across the country to do so.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_Dan_Paul.jpg\" alt=\"Daniel Magliola, Paul Reece, and me at RubyConf 2023\"><\/figure><p><a href=\"https:\/\/ruby.social\/@dmagliola\">Daniel Magliola<\/a> is a friend I&rsquo;m so delighted to see at conferences. Whether he&rsquo;s speaking or attending, conferences are better when he&rsquo;s there.<\/p><p><a href=\"https:\/\/ruby.social\/@paulreece_\">Paul Reece<\/a> shared a <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/530118\">fantastic talk<\/a> about Ruby Polars. I had the pleasure to see the talk in advance as it was being built. It was a real treat to witness the evolution.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_Ufuk_Drew.jpg\" alt=\"Me, Ufuk Kayserilioglu, and Drew Bragg at RubyConf 2023\"><\/figure><p><a href=\"https:\/\/ruby.social\/@ufuk\">Ufuk Kayserilioglu<\/a> is a pillar of our community. I&rsquo;m so pleased to see that he was recently named to the Ruby Central board. I&rsquo;m excited to see what the organization will accomplish with his stewardship. Ufuk gives the most tremendous feedback and is so engaging in conversation.<\/p><p><a href=\"https:\/\/ruby.social\/@DRBragg\">Drew Bragg<\/a> is a podcaster, game show host, and terrific human being. I&rsquo;m extremely fortunate to ride the coattails of his conference energy.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_Noel.jpg\" alt=\"Noel Rappin and me at RubyConf 2023\"><\/figure><p><a href=\"https:\/\/ruby.social\/@noelrap\">Noel Rappin<\/a> is an author and personal inspiration. I love reading his words and was so glad to talk with him in person.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/rubyconf_2023_Tim.jpg\" alt=\"Tim Riley and me at RubyConf 2023\"><\/figure><p><a href=\"https:\/\/ruby.social\/@timriley\">Tim Riley<\/a> is on the core team at Hanami, dry-rb, and rom-rb. He <a href=\"https:\/\/rubyconf-2023.sessionize.com\/session\/518035\">spoke<\/a> about Hanami and led a Community Day table working on Hanami. It was a real treat to meet and explore San Diego a bit with him this week.<\/p><h2 id=\"until-next-time-ruby-friends\">Until Next Time Ruby Friends <a href=\"#until-next-time-ruby-friends\">\ud83d\udd17<\/a><\/h2><p>Thanks to all the #RubyFriends I met at RubyConf 2023, whether we got a picture or not. I hope everyone had a great conference.<\/p>"},{"title":"Ruby for All Podcast Guest Appearance","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2023\/","pubDate":"Thu, 02 Nov 2023 20:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-for-all-guest-2023\/","description":"<h2 id=\"ruby-for-all-podcast\">Ruby for All Podcast <a href=\"#ruby-for-all-podcast\">\ud83d\udd17<\/a><\/h2><p>I was a guest on <a href=\"https:\/\/www.rubyforall.com\/s2\/50\">episode 50<\/a> of the Ruby forAll podcast. I joined Julie and Andrew to talk about conference speaking (and attending).<\/p><p>From the episode description:<\/p><blockquote><p>In this episode of Ruby for All, join Andrew and Julie as they welcome Kevin Murphy, a software developer at Pubmark, to discuss the fascinating world of speaking at conferences. Kevin shares his journey as a speaker and describes his experiences at conferences. The conversation dives into the nuances of submitting conference talk proposals, the challenges in the review process, and the importance of feedback. They also explore of the dynamics of attending and presenting at conferences, both virtually and in person, and they share some valuable advice for those interested in sharing their expertise through speaking engagements. Don\u2019t miss this insightful and supportive discussion about the Ruby community and the art of conference talks.<\/p><\/blockquote><p>Give it a <a href=\"https:\/\/www.rubyforall.com\/s2\/50\">listen<\/a> and let me know what youthink!<\/p>"},{"title":"Preparing Conference Talk Delivery","link":"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/","pubDate":"Sat, 14 Oct 2023 18:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/","description":"<h2 id=\"conference-talk-preparation-series\">Conference Talk Preparation Series <a href=\"#conference-talk-preparation-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/\">Sharing Past Conference Proposals<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">Building Conference Talk Content<\/a><\/li><li><strong>Preparing Conference Talk Delivery<\/strong><\/li><\/ol><p>A conference accepted my proposal and I&rsquo;ve built a slide deck. I still have a lot to do before hitting the stage!<\/p><h2 id=\"practice\">Practice <a href=\"#practice\">\ud83d\udd17<\/a><\/h2><p>At this point, I have a continual routine to get more familiar and comfortable with the material. It&rsquo;s also an opportunity to exercise the content on a continuous basis. The editing process isn&rsquo;t over here. However, it&rsquo;s more constrained and specific. I may be toying with the layout of a specific slide. Fighting with exactly the right word to describe a concept. Determining how to best highlight one aspect of what I&rsquo;m saying.<\/p><p>I get the hint to make those targeted edits through practicing. I close the door to my office, stand up, and deliver the talk to an empty room. No more than once a day, and not every day, starting about a month out from the conference. And I time it, just like before.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/Anyone_Can_Play_Guitar_Practice_Timing.png\" alt=\"A spreadsheet keeping track of how long sections of my presentation are taking when practicing.\"><\/figure><p>I keep track of those times. That helps me reflect on knowing if I&rsquo;m struggling to get through one section and need to tighten it up. It helps me know if I&rsquo;m giving each section its appropriate focus. And looking back over the last few practices, it helps me see where I&rsquo;ve gotten into a comfortable groove.<\/p><p>Each practice will have a bit of variation, because I don&rsquo;t follow an exact script. Over time, I&rsquo;ll see that I&rsquo;m being more consistent in how long each area is taking (plus\/minus 10 seconds or so per section). That tells me that I&rsquo;m able to repeatedly deliver.<\/p><p>The stage <em>is<\/em> a different experience, but this preparation lets me draw from all the practice. It&rsquo;s not like I black out or go on auto-pilot mode, but I&rsquo;m able to leverage all that work on stage. No one else in the audience knows what I&rsquo;m going to say next as well as me, and that gives me comfort. People may be smarter than me. People may be more familiar with the topic. No one is going to say it the same way as I am right now, and that gives me confidence.<\/p><p>For the talk from the spreadsheet above, I logged 20 practice sessions.<\/p><h2 id=\"practice-1\">Practice <a href=\"#practice-1\">\ud83d\udd17<\/a><\/h2><p>I find other places to practice separated from my slides. When I would commute to work, I would practice (in my head) on the train ride in or back from work. I practice the full talk or sections while on walks. Some times, I run through ideas for my intro or conclusion as I&rsquo;m drifting off to sleep.<\/p><p>I tell myself I want to be able to deliver my talk without the help of the slides. In case the power goes out and I need to entertain everyone in the dark, or outside, I can. Note that almost happened at RubyConf Mini 2022, when my laptop <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/#appreciation\">wouldn&rsquo;t connect<\/a> to the projector.<\/p><blockquote><p>When I\u2019m practicing, I tell myself I need to prepare enough to give my talk without any slides or notes. I practice on walks, away from my material. I practice looking the other direction (checking occasionally to make sure I\u2019m clicking to the right slide). In those moments on stage, I was wondering if my preparation would become necessary. Would I have to give my full talk without any slides?<\/p><p>Thanks Drew Bragg for unflinchingly handing me his laptop from the front row, letting me git clone the repo that has my slides on it. I ran the presentation from his computer, having never used it before.<\/p><\/blockquote><p>Even without that dramatic scenario coming to light, practicing without slides helps me. I&rsquo;m not tied to my slides. I don&rsquo;t need to read them. I can focus on the audience.<\/p><h2 id=\"practice-2\">Practice <a href=\"#practice-2\">\ud83d\udd17<\/a><\/h2><p>I find a real audience before the conference to practice with. Most times, that ends up being at work. There are also local meetup groups I could request to speak at. I could gather a group of friends.<\/p><p>My main goal is to give me the experience of delivering the talk for other people. It can help me know what beats might get laughs (rare, but I try). I might be able to see where people are more or less interested. I&rsquo;ll admit, that&rsquo;s hard for me to focus on while I&rsquo;m also delivering the talk. It&rsquo;s to help <em>me<\/em> get more comfortable.<\/p><p>It&rsquo;s also a natural place for feedback, if you want it. Feedback can be very helpful <strong>and<\/strong> very dangerous. You&rsquo;re putting yourself out there and are being vulnerable. You may not like, or want, the feedback. That&rsquo;s something you need to decide for yourself. I <em>consider<\/em> the feedback I get, but I do not <strong>incorporate<\/strong> all of it. That doesn&rsquo;t mean it&rsquo;s bad - it may be objectively right. But if I can&rsquo;t find a way to incorporate it in a way that makes me comfortable on stage, I ignore it. If it takes away from my goals of what I want the presentation to deliver, I leave it be. I&rsquo;ve given it the time and consideration, and thoughtfully chosen to do something else. That&rsquo;s <strong>hard<\/strong> to do, but I&rsquo;ve gotten better at it, as I&rsquo;ve gotten more comfortable knowing what my style is on stage.<\/p><h2 id=\"final-preparation\">Final Preparation <a href=\"#final-preparation\">\ud83d\udd17<\/a><\/h2><p>I store my slides in a GitHub repo, even while I&rsquo;m developing it. That repo is public, which has the added benefit of being shareable, should I need to use another computer. I prefer using Keynote to any online tools, but that does limit the ease of access. Something new, given my RubyConf Mini issue, is to have a buddy with the slide deck on their laptop PRIOR. That may be a conference organizer. That way it&rsquo;s already set up, and I can confirm it looks exactly how I want. I use custom fonts and colors, and I want to make sure those get installed and are available too.<\/p><p>Going back to RubyConf Mini, I was very relieved to use Drew&rsquo;s computer, but it wasn&rsquo;t the experience I wanted it to be. We were installing it live on stage. I didn&rsquo;t have the time (or foresight) to install the fonts and colors I built the deck with. It&rsquo;s a touch that only I could have noticed, and didn&rsquo;t take away too much (I hope) from the experience. But it was still frustrating. It wasn&rsquo;t the exact visual presentation I had spent the time preparing for the audience.<\/p><p>I build a page on my website about each of my talks now. At the end of the presentation, I share that link. That can provide information about the talk. I include the proposal, the slides, code examples, and related blog posts. Some times I put details about the slides themselves, like photo credits and fonts used.<\/p><p>Once I&rsquo;m confident they won&rsquo;t change, I publish my slides for the audience to access. I use <a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/\">Speaker Deck<\/a> to host my slides.<\/p><h2 id=\"packing\">Packing <a href=\"#packing\">\ud83d\udd17<\/a><\/h2><p>Here are some things I bring to a conference in service of the talk. Even if I&rsquo;m not speaking, I carry these with me, in case another speaker could use them. So, if you&rsquo;re at a conference I&rsquo;m at and you&rsquo;re looking for one of these, come find me!<\/p><ul><li>A button-up shirt - I wear these shirts so the wire of the lav mic can hide inside the shirt. Then I don&rsquo;t hit it if I&rsquo;m waving my hands around or something.<\/li><li>Water bottle - Water is good. You should drink some before and during the talk&hellip;but not too much.<\/li><li>Clear plastic cup - I usually take this from my hotel room. I may not want to fumble with taking the cap on and off my water bottle, so I pour some water in the cup on stage.<\/li><li>Slide advancer - I like to walk a bit away from the podium in a small box. Holding this gives me the freedom to do that without needing to hop back to the computer to get to the next slide.<\/li><li>Extra batteries - It&rsquo;d be a real shame if I couldn&rsquo;t advance the slides from my fancy slide advancer because I used up the batteries with all my practicing.<\/li><li>Laptop power cable - Especially if you practice with your laptop plugged in, give the talk with your laptop plugged in. You don&rsquo;t want some unexpected power saver setting surprising you on stage.<\/li><li>A\/V connection or cables - The venue or organizers may say they&rsquo;ll have some, and you may need to use them, but better to have one of your own in case.<\/li><li>Throat lozenges - I put one in my mouth about an hour before I&rsquo;m on stage, whether I need it or not.<\/li><\/ul><h2 id=\"at-the-conference\">At The Conference <a href=\"#at-the-conference\">\ud83d\udd17<\/a><\/h2><p>I find the room I&rsquo;m speaking in well before my time. Ideally a day before. At the very least, it resolves the immediate stress of needing to know where to go. I also ask to plug in my computer and make sure it works. This is a great chance to meet the A\/V team that&rsquo;ll be helping you. I introduce myself and ask their names. I ask them if there&rsquo;s anything I can do to make their jobs easier. I let them know if I&rsquo;m playing any audio on my computer. I tell them I&rsquo;m likely to walk around (a little) on stage, and ask if that&rsquo;s any concern for them.<\/p><p>I go to the slide that has the smallest font, or most dense visuals on it, and then run (ok, walk) to the back of the room. I ensure it looks legible to me.<\/p><p>That is the <em>only<\/em> time I would consider making <strong>any<\/strong> changes to my slides, content, or delivery this late. Anything else would be detrimental to the experience. I&rsquo;ve already practiced so much with it another way I do not feel comfortable making any other changes.<\/p><p>It may turn out there&rsquo;s a giant chandelier that obscures some of the projection. It&rsquo;s probably a good idea to take that into account for your audience.<\/p><p>If the schedule allows, I also see a talk in that room before my session. I sit in the back.<\/p><p>I practice the night before, by myself, in my hotel room. And only once. That rule still applies. Other speakers may form groups to do a run through or practice. I&rsquo;m happy to attend those and support others, but it doesn&rsquo;t work for me to practice that way. It&rsquo;s a lot of energy for me to speak in front of a crowd, so I save it for the stage.<\/p><h2 id=\"day-of\">Day Of <a href=\"#day-of\">\ud83d\udd17<\/a><\/h2><p>As hard as it is, I try not to think too much about my talk before I give it. I&rsquo;ve already practiced dozens of times. At this point, it is as good as it&rsquo;s going to be. This is easier said than done, but the goal is to not focus on it.<\/p><p>Be mindful of the schedule. Is there a short break before the talk? If so, then I stay in that room for the prior session, even if there&rsquo;s somewhere else I&rsquo;d rather be. Whether there&rsquo;s a long or short break before, plan on people opening the door(s) and coming in late. That makes it less surprising when it happens.<\/p><p>If given the choice, I do not take questions from the large group. I&rsquo;m happy to talk to anyone and everyone afterwards individually. I let the audience know where I&rsquo;ll be right after the talk. That&rsquo;s usually right next to the stage, but again, I&rsquo;m mindful of the schedule. Is there&rsquo;s another talk right after? I want to cede that space to the next speaker to not interrupt their preparation.<\/p><p>After you&rsquo;re done, listen to your body. Do you need to go disappear and be by yourself? Do that! Do you need the affirmation of others? Find someone in the audience and go talk to them! You did it! Think about the special dessert you&rsquo;ll get at dinner to celebrate, even if that&rsquo;s alone in your hotel room.<\/p>"},{"title":"Ruby on Rails Podcast Co-Host?!?","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-cohost\/","pubDate":"Thu, 21 Sep 2023 08:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-cohost\/","description":"<h2 id=\"the-ruby-on-rails-podcast\">The Ruby on Rails Podcast <a href=\"#the-ruby-on-rails-podcast\">\ud83d\udd17<\/a><\/h2><p>I co-hosted <a href=\"https:\/\/www.therubyonrailspodcast.com\/488\">episode 488<\/a> of The Ruby on Rails Podcast. Brittany and I talked with Allison McMillan and Chelsea Kaufman about all the plans for RubyConf 2023.<\/p><p>All the thanks to Brittany Martin for asking me to join. I didn&rsquo;t have podcast hosting on my career bingo card.<\/p><iframe src=\"https:\/\/player.fireside.fm\/v2\/3OC19MC9+EypbCECp?theme=dark\" width=\"740\" height=\"200\" frameborder=\"0\" scrolling=\"no\"><\/iframe>"},{"title":"Building Conference Talk Content","link":"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/","pubDate":"Fri, 15 Sep 2023 18:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/","description":"<h2 id=\"conference-talk-preparation-series\">Conference Talk Preparation Series <a href=\"#conference-talk-preparation-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/\">Sharing Past Conference Proposals<\/a><\/li><li><strong>Building Conference Talk Content<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/\">Preparing Conference Talk Delivery<\/a><\/li><\/ol><p>Have you recently had a conference proposal accepted? Congratulations! Are you wondering, &ldquo;what do I do <em>now<\/em>?&rdquo; Here is the process I follow to turn my proposal into a full-length talk.<\/p><h2 id=\"content-generation\">Content Generation <a href=\"#content-generation\">\ud83d\udd17<\/a><\/h2><p>My first step is to gather all the content I want to talk about as quickly as possible. I know that sounds reductive. Oh, the first step is JUST to put together all your content? How easy! I might as well tell you <a href=\"https:\/\/knowyourmeme.com\/photos\/572078-how-to-draw-an-owl\">how to draw an owl<\/a> next.<\/p><p>But, here&rsquo;s the thing - for a conference talk, I&rsquo;m not starting from scratch. I&rsquo;ve already put a lot of time, and thinking, into the <a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/\">proposal<\/a>. For me, that almost always includes a full outline. So I start by revisiting that and see what I need to add.<\/p><p>I completed most of my research in the proposal, but I may need to brush up on some details. There may be a book or research paper that I skimmed that I need to go back to and dig into.<\/p><p>Will I be using code examples to demonstrate some points? I&rsquo;ll start writing some code samples, in my usual code editor, that show what I want. I&rsquo;ll make sure they run. At this point, I&rsquo;m not concerned about how terse or verbose they are. I&rsquo;m not worried what the domain is. I need something that shows the point I want to make. I will admit that sometimes this code generation process is procrastination. I try to call myself out on that and not put too much effort into them at this point.<\/p><h2 id=\"shameless-green\">Shameless Green <a href=\"#shameless-green\">\ud83d\udd17<\/a><\/h2><p>My next step is to put my thoughts together on slides. I use the default theme. White background with black text. Whatever the default font is. I do not care about slide design <strong>at all<\/strong> at this point.<\/p><p>Some slides are one word. Some are an emoji. Some are a dense block of code. Some start as bullet points. Sometimes it&rsquo;s a thought about what I <em>want<\/em> the slide to eventually be, like, &ldquo;insert picture of an owl&rdquo;. Everything is a placeholder at this point.<\/p><p>I call this process &ldquo;Shameless Green&rdquo;. I borrowed this term from the book <a href=\"https:\/\/sandimetz.com\/99bottles-sample-ruby#_shameless_green\">&ldquo;99 Bottles of OOP&rdquo;<\/a> by Sandi Metz, Katrina Owen, and TJ Stankus. It doesn&rsquo;t match exactly, but it&rsquo;s stuck in my head, so I continue to use it. Intentionally reminding myself to be shameless gives me more license to do that. There&rsquo;s no polish here. This isn&rsquo;t even a first draft; it&rsquo;s only for me - there&rsquo;s no chance of another human seeing this.<\/p><h2 id=\"first-delivery\">First Delivery <a href=\"#first-delivery\">\ud83d\udd17<\/a><\/h2><p>After this, I deliver the talk. No, I don&rsquo;t get on stage with it. I don&rsquo;t have an audience. I close the door to my office, stand up, and speak out loud as if I were on a stage - with my slides in front of me on my computer.<\/p><p>This will be <em>rough<\/em>. I&rsquo;ll feel uncomfortable. I stop to write down when those feelings hit. Not enough to pull me out of my rhythm, but I don&rsquo;t want to forget it.<\/p><p>Some things I might write:<\/p><ul><li>Slide 7 - awkward intro<\/li><li>Slide 13 - example doesn&rsquo;t work<\/li><li>Slide 16 - missing context<\/li><li>Slide 32 - repeating info from earlier<\/li><\/ul><p>After I&rsquo;ve gone through everything, I take a few minutes to review my notes. Only to make sure everything is legible and makes sense. I take the time to add more details where needed.<\/p><p>And then I walk away. I&rsquo;ll revisit it later with a fresh perspective.<\/p><h2 id=\"editing\">Editing <a href=\"#editing\">\ud83d\udd17<\/a><\/h2><p>I take a few hours to a day away from my initial attempt. I then review the notes while iterating through the slides. What I&rsquo;m looking for here is any <em>structural<\/em> changes I need to make.<\/p><p>Is there a different order I should present information in? Maybe the section in Slide 32 needs to happen closer to Slide 16. That&rsquo;ll add more context immediately and eliminate the need to repeat myself later on.<\/p><p>If there are any transitions that feel awkward, I dig into why. Am I missing information? Do I have unnecessary information? Do I need to reorder the content? Is there a connection that I need to make more explicit, for myself and the audience?<\/p><p>These changes likely don&rsquo;t get done in one sitting. I revisit a section, or a transition, at a time. Then I review everything in total again. That might spark another round of smaller sections to focus on.<\/p><h2 id=\"theme\">Theme <a href=\"#theme\">\ud83d\udd17<\/a><\/h2><p>Now I&rsquo;m comfortable with the general flow of information I want to share with the audience. Next, I build my <strong>theme<\/strong>. I still don&rsquo;t mean slide design here. I mean the real or imaginary <a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/#go-all-in-with-a-theme\">world<\/a> that my talk will be about. Some talks have a theme as part of their proposal. For others, I need to develop it at this point.<\/p><p>A theme is important for me in a talk because it turns my presentation into a story. I have a hard time delivering information over that period of time in hard, technical terms. Other people can, and that&rsquo;s great! It doesn&rsquo;t work for me. I need to have some real or imaginary scenario that the content relates to. Most of the time, this involves creating an imaginary product or company. That will illustrate the topics I&rsquo;m sharing with the audience.<\/p><p>I may need to change all my examples to match the theme. The code samples now should reflect that theme. Let&rsquo;s say a talk is using a theme of managing scientific study participants. Code that was using ActiveRecord callbacks to send an email is now sending an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Informed_consent\">informed consent<\/a> document.<\/p><h2 id=\"second-delivery\">Second Delivery <a href=\"#second-delivery\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve reworked my transitions, moved sections around, and built up a story around my content. Now it&rsquo;s time to run through the presentation again. This time, I take fewer notes, but I also add something.<\/p><p>In this practice session, I start timing the presentation. Where I have discernible sections, I track those as splits\/laps (depending on your timer of choice). Talks need to fit within a defined schedule. This is nowhere near polished. Still, the timing gives me a helpful sign of how far off I am from where I need to be.<\/p><p>Most of the time, I discover I&rsquo;m way over on time.<\/p><h2 id=\"editing-1\">Editing <a href=\"#editing-1\">\ud83d\udd17<\/a><\/h2><p>In this editing session, I&rsquo;m incorporating the same process from the first edit, but I&rsquo;m adding to it. What am I going to cut entirely? Usually at this point I know what I want to say will take too long and I need to remove pieces. Even if I&rsquo;m on time, I may recognize that there are pieces that don&rsquo;t complement my goal.<\/p><p>This is why I focus on &ldquo;shameless green&rdquo; to start. I haven&rsquo;t invested in picking colors or graphics for my slides. I haven&rsquo;t pored over detailed animations and visuals. Getting rid of content isn&rsquo;t <em>easy<\/em>, but it&rsquo;s harder (for me) the more work I&rsquo;ve put into it.<\/p><p>This doesn&rsquo;t mean you&rsquo;ve lost all that work you cut forever. First off, it was <strong>necessary<\/strong> to get what remains to where it is now. You learned something as part of putting that together that&rsquo;ll help you.<\/p><p>You can also reuse it elsewhere. I&rsquo;ve turned sections of presentations I&rsquo;ve had to cut into entirely different talks. Some have become blog posts. If you&rsquo;re not willing to let it go, but it doesn&rsquo;t fit in this format, it doesn&rsquo;t need to be the end. It needs to go somewhere else.<\/p><h2 id=\"slide-design\">Slide Design <a href=\"#slide-design\">\ud83d\udd17<\/a><\/h2><p>I am by no means a designer, but I do my best to deliver pleasant slides. Here are some things I look for.<\/p><p>Does the slide complement what I&rsquo;m saying? My speech and the slide should work together, but not be the same. They should highlight what&rsquo;s important without being repetitive. How can I replace words with visuals? I&rsquo;ve turned sentences of text into a single emoji. I&rsquo;ve replaced them with a picture. I try to limit the text on the screen. I want my audience mostly listening to what I&rsquo;m saying, not reading what&rsquo;s behind me.<\/p><p>Are the slides cohesive? I use slide templates to use consistent fonts, color schemes, and layouts. This also makes incorporating large design changes easier later on. Rather than needing to change 80 slides individually, I change the template.<\/p><p>Are my slides accessible? I run my color scheme through a <a href=\"https:\/\/webaim.org\/resources\/contrastchecker\/\">contrast checker<\/a>. I strive for a WCAG AAA level of contrast between my colors. I typically pick no more than four colors. A consistent background color. A consistent text color. No more than two colors for highlighting or drawing attention to parts of the slide.<\/p><p>Are my slides readable to the audience? I avoid putting any information you need the audience to see on the bottom third of the slides. Someone&rsquo;s head might be in the way. I distill code down to the essentials, even if that means it isn&rsquo;t syntactically valid or complete. I use as large of a font size and as few words as necessary.<\/p><p>I have what&rsquo;s showing on the slide be what you want the audience to focus on. Not what you&rsquo;re going to tell them in two more minutes. To help me have more control over this, if I ever use any slide builds, each build is a separate slide. I don&rsquo;t use an animation that happens within one slide. It makes it easier to go back and forth between slides, if necessary.<\/p><h2 id=\"next-steps\">Next Steps <a href=\"#next-steps\">\ud83d\udd17<\/a><\/h2><p>I have my slides built at this point, but my work isn&rsquo;t over. Next I go through the laborious, repetitive process to provide a <a href=\"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/\">consistent delivery<\/a>.<\/p>"},{"title":"Keeping Up With Ruby News All Week Long","link":"https:\/\/kevinjmurphy.com\/posts\/keeping-up-with-ruby-news\/","pubDate":"Sat, 19 Aug 2023 15:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/keeping-up-with-ruby-news\/","description":"<p>I read a lot of Ruby news throughout the week. When someone asks me what they should follow, I first suggest that they not follow exactly the same blogs I do. Then I share the RSS feeds I follow just in case. But what I do suggest is starting with the following places. These highlight the work of others on a weekly basis. I then suggest they keep up with what resonates with them inside these round-ups.<\/p><h2 id=\"sunday\">Sunday <a href=\"#sunday\">\ud83d\udd17<\/a><\/h2><p>As you get ready to start your work week, what&rsquo;s new out there that can get you excited to write some code? Check out the <a href=\"https:\/\/www.rubyradar.com\/\">Ruby Radar<\/a> newsletter. The format shows two social media posts, two articles, two podcasts, two videos, and two code updates (commits, releases, projects).<\/p><h2 id=\"monday\">Monday <a href=\"#monday\">\ud83d\udd17<\/a><\/h2><p>Waiting in your inbox as you start your week is the <a href=\"https:\/\/newsletter.shortruby.com\/\">Short Ruby Newsletter<\/a>. Naming is hard, and this is the most comprehensive round-up of information I follow. Lucian curates a highlighted list of information about our community, events, code and Ruby, gems and libraries, and related (not Ruby specific) updates.<\/p><p>If that&rsquo;s not enough for you, there&rsquo;s more. That&rsquo;s followed with a list of other newsletters, podcasts, videos, and articles of the week. You&rsquo;ll likely have a lot of tabs open after getting through a full edition of Short Ruby.<\/p><h2 id=\"tuesday\">Tuesday <a href=\"#tuesday\">\ud83d\udd17<\/a><\/h2><p>After all that, you might need a break from reading. Instead, catch up with the newest episode of <a href=\"https:\/\/www.bikeshed.fm\/\">The Bike Shed<\/a> podcast. While not Ruby-specific, the current hosts Jo\u00ebl Quenneville and Stephanie Minn frequently discuss Ruby topics. This thoughtbot podcast discusses what&rsquo;s new in these consultant&rsquo;s worlds from week to week.<\/p><h2 id=\"wednesday\">Wednesday <a href=\"#wednesday\">\ud83d\udd17<\/a><\/h2><p>You can continue with a podcast today with a new episode of <a href=\"https:\/\/www.therubyonrailspodcast.com\/\">The Ruby On Rails Podcast<\/a>. It&rsquo;s hosted by Brittany Martin and a rotating panel of co-hosts. This is typically structured as an interview with a guest.<\/p><h2 id=\"thursday\">Thursday <a href=\"#thursday\">\ud83d\udd17<\/a><\/h2><p>Thursday is a big day for Ruby news. The <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> newsletter comes out on this day. On top of the hottest news and updates of the week, this highly-curated newsletter provides a sample of tutorials, articles, videos, code, and tools that updated throughout the week.<\/p><p>Later in the day, dig in to the <a href=\"https:\/\/ruby.libhunt.com\/\">Awesome Ruby<\/a> newsletter with more popular news and articles, followed with trending projects.<\/p><p>There is also the <a href=\"https:\/\/www.rubyforall.com\/\">Ruby for All<\/a> podcast. The topics can sometimes seem targeted to early-career developers. I&rsquo;ve found that makes it an engaging conversation for everyone to follow and learn from.<\/p><h2 id=\"friday\">Friday <a href=\"#friday\">\ud83d\udd17<\/a><\/h2><p>You can check out a summary of commits and PRs specific to Rails with the <a href=\"https:\/\/world.hey.com\/this.week.in.rails\">This Week In Rails<\/a> newsletter.<\/p><p>Entertain yourself at the end of the week with the <a href=\"https:\/\/remoteruby.com\/\">Remote Ruby<\/a> podcast. Andrew Mason, Chris Oliver, and Jason Charnes discuss the latest in what they&rsquo;re working on or interested in. Occasionally there are guests. Other times the panel of hosts are conversing amongst themselves.<\/p><h2 id=\"saturday\">Saturday <a href=\"#saturday\">\ud83d\udd17<\/a><\/h2><p>Take a break if you need to. Or use the time to catch up on what you&rsquo;ve missed from these places throughout the week.<\/p><h2 id=\"weekly-reflection\">Weekly Reflection <a href=\"#weekly-reflection\">\ud83d\udd17<\/a><\/h2><p>After taking in the information, decide what sources from these curated experiences to keep up with. If you enjoyed what someone had to say, their latest won&rsquo;t always be in these round-ups. Follow podcast guests on social media. Add blogs you enjoy to your RSS reader. Join writer&rsquo;s newsletters. This is how I&rsquo;ve cultivated my list of sources that I follow, whether they end up highlighted in a weekly review or not.<\/p>"},{"title":"Testing Inherited Behavior","link":"https:\/\/kevinjmurphy.com\/posts\/testing-inherited-behavior\/","pubDate":"Fri, 14 Jul 2023 19:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/testing-inherited-behavior\/","description":"<h2 id=\"getting-it-on-record\">Getting It On Record <a href=\"#getting-it-on-record\">\ud83d\udd17<\/a><\/h2><p>Recently I asked on <a href=\"https:\/\/ruby.social\/@kevin_j_m\/110668925217643586\">Mastodon<\/a>:<\/p><blockquote><p>How do you prefer to see methods that are inherited from another class unit tested?<\/p><p>I&rsquo;m applying this to #ruby code, but this question is meant to be language-agnostic to cover how you test inherited behavior in OO languages.<\/p><\/blockquote><p>This is something that I&rsquo;ve waffled on, and changed my opinion on, seemingly each time I have to deal with this. For me, a lot of the tension comes from choosing why I&rsquo;m testing. Am I testing to verify the correctness of the code, or am I testing to document expected behavior?<\/p><p>Let&rsquo;s say we&rsquo;re managing a record collection.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">VinylAlbum<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">shape<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:circle<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">audio_technology<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:analog<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">max_minutes<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">46<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This all works until we get our first <a href=\"https:\/\/en.wikipedia.org\/wiki\/Extended_play\">extended play<\/a> (EP) record. To model that, we inherit from our <code>VinylAlbum<\/code> and override <code>max_minutes<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Ep<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">VinylAlbum<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">max_minutes<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We want to test our new <code>Ep<\/code> class. What do we do? Here&rsquo;s what respondents to my poll said.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/mastodon_test_inherited_behavior_poll.png\" alt=\"A poll on Mastodon asking how people unit test inherited behavior. 67% say don&#39;t test it. 21% say something fancy (like a shared example). 13% say duplicate the test. 24 responses\"><\/figure><h2 id=\"duplicate-the-test\">Duplicate The Test <a href=\"#duplicate-the-test\">\ud83d\udd17<\/a><\/h2><p>This was the least popular response (holding aside &ldquo;Other&rdquo;, which no one answered with). In this case, you can consider the inheritance to be an implementation detail. <code>Ep<\/code> responds to three methods, so I&rsquo;m going to test how it behaves to receiving those three messages.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">Ep<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#shape&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is a circle&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> album <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Ep<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>shape)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#e6db74\">:circle<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;audio_technology&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is analog&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> album <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Ep<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>audio_technology)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#e6db74\">:analog<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#max_minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is 30 minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> album <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Ep<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>max_minutes)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This is documenting the public API surface of the unit under test. I&rsquo;m signaling when reading or running these tests that <code>Ep<\/code> has these methods and responds in this way to them.<\/p><p>The cost here is in maintaining these tests. Any changes to <code>VinylRecord<\/code> will mean changing not only its tests, but also <code>Ep<\/code>&rsquo;s tests. These tests also run in the regression suite forever. That contributes time to run tests that you might consider to be testing the same thing twice.<\/p><h3 id=\"testing-inherited-behavior-creates-an-integration-test\">Testing Inherited Behavior Creates An Integration Test <a href=\"#testing-inherited-behavior-creates-an-integration-test\">\ud83d\udd17<\/a><\/h3><p>I also received <a href=\"https:\/\/ruby.social\/@acute_distress\/110672196493618303\">feedback<\/a> to consider testing in this way an integration test. And my question was specifically about <em>unit testing<\/em>.<\/p><blockquote><p>only test what YOUR UNIT does and nothing else&hellip;Otherwise you are writing integration tests. It\u2019s a grey area especially if you have added a private method to the child. Then you\u2019ve got to ask yourself is your design correct.<\/p><\/blockquote><p>I don&rsquo;t think this opinion is wrong. It may be a little at odds with how free I <em>tend<\/em> to see people be with the term &ldquo;unit test&rdquo;. Particularly in the Ruby, and especially Rails, community. I&rsquo;ve seen more than my share of what we call unit tests of models in a Rails app that hit the database. That test is not <strong>technically<\/strong> a unit test. It&rsquo;s not only collaborating with another class. It&rsquo;s working with a whole other <em>external dependency<\/em>. Testing the behavior we&rsquo;re inheriting doesn&rsquo;t seem that out of line in comparison.<\/p><h3 id=\"wheres-the-line\">Where&rsquo;s The Line? <a href=\"#wheres-the-line\">\ud83d\udd17<\/a><\/h3><p>Let&rsquo;s take this to the extreme though and look at a class that has no behavior defined in Ruby.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Foo<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> foo <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Foo<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> foo<span style=\"color:#f92672\">.<\/span>public_methods<span style=\"color:#f92672\">.<\/span>count<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">64<\/span><\/span><\/span><\/code><\/pre><\/div><p>Are you writing tests for those 64 methods you get from <code>BasicObject<\/code> and wherever else? I know I&rsquo;m not.<\/p><p>Maybe instead we say we&rsquo;ll only test behavior that we explicitly inherit.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Foo<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ApplicationRecord<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> foo <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Foo<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> foo<span style=\"color:#f92672\">.<\/span>public_methods<span style=\"color:#f92672\">.<\/span>count<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">466<\/span><\/span><\/span><\/code><\/pre><\/div><p>I&rsquo;m not writing tests for all the behavior I get from Active Record either.<\/p><p>So why duplicate the tests? It seems here that the decision is to test behavior inherited from code that I own, or that&rsquo;s defined in my codebase. I&rsquo;m trusting that Active Record is testing its behavior. I don&rsquo;t trust <em>my<\/em> behavior I inherit from <code>VinylRecord<\/code>.<\/p><h2 id=\"dont-test\">Don&rsquo;t Test <a href=\"#dont-test\">\ud83d\udd17<\/a><\/h2><p>This was the most popular option. We trust Ruby to test the methods it defines. And we trust Active Record to test the methods it defines. As opposed to the duplication section, here we <em>also<\/em> trust <code>VinylAlbum<\/code> to test the methods it defines.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">Ep<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#max_minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is 30 minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> album <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Ep<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>max_minutes)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The <code>VinylAlbum<\/code> tests are already testing <code>shape<\/code> and <code>audio_technology<\/code>. <code>max_minutes<\/code> is different, and defined in the implementation of the <code>Ep<\/code> class, so we only test that.<\/p><h2 id=\"something-fancy-shared-examples\">Something Fancy (Shared Examples) <a href=\"#something-fancy-shared-examples\">\ud83d\udd17<\/a><\/h2><p>This was the second most popular response. I suggested a <a href=\"https:\/\/rspec.info\/features\/3-12\/rspec-core\/example-groups\/shared-examples\/\">shared example<\/a> as an implementation of something fancy. I received no responses explaining another fancy method. So I&rsquo;m assuming that all the respondents meant a shared example.<\/p><p>For this, we document the behavior that makes up a vinyl album.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>shared_examples <span style=\"color:#e6db74\">&#34;an album&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#shape&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is a circle&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>shape)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#e6db74\">:circle<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;audio_technology&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is analog&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>audio_technology)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#e6db74\">:analog<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#max_minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is 30 minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>max_minutes)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>And then each of the classes that have this behavior include this shared example in their tests.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">VinylAlbum<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">Ep<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This tests the full API surface of the classes inheriting this behavior. That may satisfy a need you feel where you&rsquo;d otherwise duplicate the test. All that without actually having to write the tests over again.<\/p><p>There&rsquo;s just one problem: the <code>Ep<\/code> tests don&rsquo;t pass. That&rsquo;s because of the <code>max_minutes<\/code> method, which is overridden. An EP can&rsquo;t be 46 minutes; it can be no more than 30.<\/p><p>Now, we can get around that a couple of ways.<\/p><h3 id=\"passing-parameters-to-the-shared-example\">Passing Parameters To The Shared Example <a href=\"#passing-parameters-to-the-shared-example\">\ud83d\udd17<\/a><\/h3><p>We can pass a parameter to the shared example.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>shared_examples <span style=\"color:#e6db74\">&#34;an album&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>max_minutes<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#max_minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;is <\/span><span style=\"color:#e6db74\">#{<\/span>max_minutes<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\"> minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>max_minutes)<span style=\"color:#f92672\">.<\/span>to eq max_minutes<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>And then specify that in each of our usages of the shared example.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">VinylAlbum<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span>, <span style=\"color:#ae81ff\">46<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">Ep<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span>, <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The tests now pass. However, I&rsquo;d argue this is a bit confusing. Read the invocation of the shared example in a vacuum. You have no idea what that number has to do with the shared example. It causes the test to pass, but it&rsquo;s lacking explanatory information. You need to read the shared example to see what 46 or 30 refers to.<\/p><h3 id=\"passing-context-to-the-shared-example\">Passing Context To The Shared Example <a href=\"#passing-context-to-the-shared-example\">\ud83d\udd17<\/a><\/h3><p>We can provide it a name by instead passing the context of that number to the shared example. The shared example will implicitly use an <code>expected_max_minutes<\/code> reference.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>shared_examples <span style=\"color:#e6db74\">&#34;an album&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> describe <span style=\"color:#e6db74\">&#34;#max_minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;has a maximum number of minutes&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(album<span style=\"color:#f92672\">.<\/span>max_minutes)<span style=\"color:#f92672\">.<\/span>to eq expected_max_minutes<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Each of the tests will define <code>expected_max_minutes<\/code> in a block.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">VinylAlbum<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> let(<span style=\"color:#e6db74\">:expected_max_minutes<\/span>) { <span style=\"color:#ae81ff\">46<\/span> }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>describe <span style=\"color:#66d9ef\">Ep<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it_behaves_like <span style=\"color:#e6db74\">&#34;an album&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> let(<span style=\"color:#e6db74\">:expected_max_minutes<\/span>) { <span style=\"color:#ae81ff\">30<\/span> }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This adds clarity about what the number refers to when we call the shared example. However, the test description had to become more vague. We can&rsquo;t use it to say how many minutes it should be. Instead, we just say it has a maximum number of minutes, whatever that may be.<\/p><h3 id=\"customizing-the-shared-example\">Customizing The Shared Example <a href=\"#customizing-the-shared-example\">\ud83d\udd17<\/a><\/h3><p>In either case, we needed to take some action to get our tests to pass. That&rsquo;s because behavior was overridden - the <code>max_minutes<\/code> method.<\/p><p>That can get unwieldy over time if you have a large inheritance tree. Whether that be with long branches or a large number of leaves on one branch. Any time there&rsquo;s a customization of the default behavior, we need to account for it. The base shared example needs to change and every use of the shared example needs to handle it.<\/p><p>We could use the shared example to test only the behavior that&rsquo;s consistent in all classes. Here that would mean <code>max_minutes<\/code> isn&rsquo;t in the shared example. Instead we&rsquo;d test that within the implementations of both the classes. That can become difficult as more classes inherit and customize behavior. We&rsquo;ll continue pulling items from the shared example. We need to have the discipline to remember to test them in all the implementing classes.<\/p><p>We could also build multiple shared examples. There could be one that tests all the behavior that is common. Anything overridden could then move into a separate shared example. All the existing inheriting classes would use that new shared example. The new class that overrides it would test its customization itself. That again requires discipline to manage as the inheritance structure grows and changes.<\/p><h2 id=\"setting-the-record-straight\">Setting The Record Straight <a href=\"#setting-the-record-straight\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ll admit - I&rsquo;m drawn to duplicating the tests. I like using my tests as documentation. I like showing what to expect it to respond to, and how it will react.<\/p><p>I also don&rsquo;t think that stands up to logical scrutiny when compared to a lot of <strong>other<\/strong> values I hold dear. That&rsquo;s where the tension comes from that led me to make this poll.<\/p><p>I don&rsquo;t test code that I don&rsquo;t own or control. As an aside, there&rsquo;s always an exception. But, in general, as already mentioned, I&rsquo;m not testing the methods that come along &ldquo;for free&rdquo; on any Ruby object. Or testing the full API surface of a class inheriting from <code>ApplicationRecord<\/code>. For some reason, I&rsquo;m called to test inherited behavior that&rsquo;s defined in the codebase.<\/p><p>I care very deeply about maintenance. I care about minimizing the negative impact that code today will have on my team tomorrow and next year. Those duplicated tests have a big cost the team needs to manage over the life of that code.<\/p><p>In that light, a shared example may feel like a welcome compromise. With a one liner, I&rsquo;ve tested all those methods and no one&rsquo;s the wiser. Except - we are. We <em>still<\/em> need to maintain them. And those tests are still running, testing the same behavior, multiple times. That makes the feedback loop from our test suite slower.<\/p><p>Not adding the tests sounds like the choice I should make, in light of what I value. However, my interest in using tests to document behavior tells me otherwise. Perhaps thinking of those tests as integration tests will help tamp down the feeling. I&rsquo;ll keep it in mind going forward.<\/p><p>Thanks to everyone who responded to my poll. Are there more topics you&rsquo;d like to see a poll about and have me write up my thoughts on? <a href=\"https:\/\/ruby.social\/@kevin_j_m\">Let me know<\/a>.<\/p>"},{"title":"Available on Bluesky","link":"https:\/\/kevinjmurphy.com\/posts\/joining-bluesky\/","pubDate":"Tue, 11 Jul 2023 15:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/joining-bluesky\/","description":"<p>I&rsquo;ve created an account on Bluesky. You can follow along and connect with me <a href=\"https:\/\/bsky.app\/profile\/kevinjm.bsky.social\">here<\/a>.<\/p><p>At the time of this writing, I have no invite codes available to share.<\/p>"},{"title":"Blue Ridge Ruby 2023 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023-recap\/","pubDate":"Sun, 11 Jun 2023 14:00:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023-recap\/","description":"<h2 id=\"blue-ridge-ruby-2023\">Blue Ridge Ruby 2023 <a href=\"#blue-ridge-ruby-2023\">\ud83d\udd17<\/a><\/h2><p>Blue Ridge Ruby recently wrapped up in Asheville, North Carolina. This post is not intended as a review of any of the talks, but to highlight the variety of great work from all involved. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you once they are available.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><h3 id=\"welcome\">Welcome <a href=\"#welcome\">\ud83d\udd17<\/a><\/h3><p>Jeremy Smith gave an intro that I hope to see expanded into a full conference talk one day. Maybe after he&rsquo;s not so busy organizing a conference.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_jeremy_smith.jpg\" alt=\"Jeremy Smith at Blue Ridge Ruby\"><\/figure><h3 id=\"enough-coverage-to-beat-the-band\">Enough Coverage To Beat The Band <a href=\"#enough-coverage-to-beat-the-band\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/kevin-murphy\/\">I<\/a> had the pleasure to bring the &ldquo;Ruby&rsquo;s Got You Covered&rdquo; <a href=\"https:\/\/kevinjmurphy.com\/posts\/coverage\/\">world tour<\/a> to Asheville. This was my first time sharing this with an in-person audience.<\/p><p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_kevin_murphy.jpeg\" alt=\"Kevin Murphy at Blue Ridge Ruby\"><\/figure>Photo Credit: Drew Bragg<\/p><h3 id=\"empathetic-pair-programming-with-nonviolent-communication\">Empathetic Pair Programming with Nonviolent Communication <a href=\"#empathetic-pair-programming-with-nonviolent-communication\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/stephanie-minn\/\">Stephanie Minn<\/a> shared a way we can reframe our mindsets and statements in pairing situations and beyond. We reviewed observations, feelings, needs, and requests within the NVC (Nonviolent Communication) framework.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_stephanie_minn.jpg\" alt=\"Stephanie Minn at Blue Ridge Ruby\"><\/figure><h3 id=\"forecasting-the-future-an-introduction-to-machine-learning-for-weather-prediction-in-native-ruby\">Forecasting the Future: An Introduction to Machine Learning for Weather Prediction in Native Ruby <a href=\"#forecasting-the-future-an-introduction-to-machine-learning-for-weather-prediction-in-native-ruby\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/landon-gray\/\">Landon Gray<\/a> gave us an appreciation for all the work that goes in to prepare, manipulate, and clean the training data used in machine learning models.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_landon_gray.jpg\" alt=\"Landon Gray at Blue Ridge Ruby\"><\/figure><h3 id=\"who-wants-to-be-a-ruby-engineer\">Who Wants To Be A Ruby Engineer? <a href=\"#who-wants-to-be-a-ruby-engineer\">\ud83d\udd17<\/a><\/h3><p>Drew Bragg, to the surprise of everyone only paying attention to the schedule on the website, hosted a small version of his popular game show after lunch.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_drew_bragg.jpg\" alt=\"Drew Bragg at Blue Ridge Ruby\"><\/figure><h3 id=\"rspec-the-bad-parts\">RSpec: The Bad Parts <a href=\"#rspec-the-bad-parts\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/caleb-hearth\/\">Caleb Hearth<\/a> demonstrated how we can use test structures that promote obviousness. Test examples should show what&rsquo;s being tested and how it is being used.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_caleb_hearth.jpeg\" alt=\"Caleb Hearth at Blue Ridge Ruby\"><\/figure><h3 id=\"maintenance-matters-maintaining-your-rails-app-and-your-sanity\">Maintenance Matters: Maintaining Your Rails App and Your Sanity <a href=\"#maintenance-matters-maintaining-your-rails-app-and-your-sanity\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/annie-kiley\/\">Annie Kiley<\/a> shared 10 suggestions to keep maintenance at the forefront of your application development process. I liked their ethos to make it easier to do the better thing, and that standards are not standards unless they&rsquo;re enforced.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_annie_kiley.jpg\" alt=\"Annie Kiley at Blue Ridge Ruby\"><\/figure><h3 id=\"making-ruby-faster\">Making Ruby Fast(er) <a href=\"#making-ruby-faster\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/kevin-menard\/\">Kevin Menard<\/a> ended the day of talks, just as a different Kevin started them. This talk starts with a discussion on Instruction Set Architectures (ISA) on silicon and transitions to YARV optimizations of Ruby code on a virtual machine (VM). Interpreters, parsers, and compilers - oh my!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_kevin_menard.jpg\" alt=\"Kevin Menard at Blue Ridge Ruby\"><\/figure><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><h3 id=\"whats-your-type-generating-type-signatures-with-sorbet-and-tapioca\">What\u2019s your type? Generating type signatures with Sorbet and Tapioca <a href=\"#whats-your-type-generating-type-signatures-with-sorbet-and-tapioca\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/emily-samp\/\">Emily Samp<\/a> reflected on her reflection experience. Through this we learned how Tapioca generates RBI files for Sorbet to use. Give this one a watch for her beautiful slide design and expert animations and transitions.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_emily_samp.jpg\" alt=\"Emily Samp at Blue Ridge Ruby\"><\/figure><h3 id=\"digital-identity-or-how-i-learned-to-stop-worrying-and-love-web3\">Digital Identity or: How I Learned to Stop Worrying and Love Web3 <a href=\"#digital-identity-or-how-i-learned-to-stop-worrying-and-love-web3\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/thomas-carr\/\">Thomas Carr<\/a> discussed the concept of a self-sovereign identity. This enables a user to manage their own data.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_thomas_carr.jpg\" alt=\"Thomas Carr at Blue Ridge Ruby\"><\/figure><h3 id=\"go-pro-with-poros\">Go Pro with POROs <a href=\"#go-pro-with-poros\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/ifat-ribon\/\">Ifat Ribon<\/a> discussed encapsulation, clarity and simplicity. Starting with a primer on database wrappers and modules, we moved into exploring different patterns where Plain Old Ruby Objects (POROs) apply: services, API wrappers, virtual domain models, and request\/presentation objects.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_ifat_ribon.jpg\" alt=\"Ifat Ribon at Blue Ridge Ruby\"><\/figure><h3 id=\"lightning-talks\">Lightning Talks <a href=\"#lightning-talks\">\ud83d\udd17<\/a><\/h3><p>Fishing, stewardship, CLI tools for generating licenses and CI results, Rails tips, hiring, 1:1s with managers, and accessibility were just a few of the topics covered in these five minute sessions.<\/p><h3 id=\"how-can-i-move-forward-when-i-dont-know-where-i-want-to-go\">How can I move forward when I don\u2019t know where I want to go? <a href=\"#how-can-i-move-forward-when-i-dont-know-where-i-want-to-go\">\ud83d\udd17<\/a><\/h3><p><a href=\"https:\/\/blueridgeruby.com\/speakers\/mo-oconnor\/\">Mo O\u2019Connor<\/a> introduced tools to guide decisions on moving forward. Individual Development Plans, applying\/interviewing, somatics, pros\/cons lists, mentorship, and building relationships were the topics covered.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_mo_oconnor.jpg\" alt=\"Mo O&#39;Connor at Blue Ridge Ruby\"><\/figure><h2 id=\"appreciation\">Appreciation <a href=\"#appreciation\">\ud83d\udd17<\/a><\/h2><p>Thank you to Jeremy Smith for putting this idea into the world and making it a reality.<\/p><p>Thank you to Mark Locklear, Karl McCollester, and Joe Peck for helping to organize.<\/p><p>Thank you to the entire Blue Ridge Ruby Team:<\/p><ul><li>Mark Locklear<\/li><li>Joe Peck<\/li><li>Karl McCollester<\/li><li>Johnathon Wright<\/li><li>Bryce Senz<\/li><li>Daniel Bradley<\/li><li>Jeremy Smith<\/li><li>Kristy Smith<\/li><li>Porter Smith<\/li><li>Jade Smith<\/li><li>Thomas Carr<\/li><li>Jay Sanders<\/li><\/ul><p>Thank you to the sponsors for supporting a fledgling event. Particular thanks to Pubmark for helping support my participation.<\/p><p>Thank you to everyone who I met there. A conference doesn&rsquo;t work unless people show up, and you all made it the event it became. Thanks to everyone for the kind words about my talk. I really appreciate it. Speaking is a lot of work - hearing from people afterwards is what makes it worth it.<\/p><p>Congrats Jeremy; you did it.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/blue_ridge_ruby_jeremy_smith_close.jpg\" alt=\"Jeremy Smith closing Blue Ridge Ruby\"><\/figure>"},{"title":"Flog-Driven Development","link":"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/","pubDate":"Sat, 13 May 2023 07:20:24 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/","description":"<h2 id=\"anyone-can-play-guitar-series\">Anyone Can Play Guitar Series <a href=\"#anyone-can-play-guitar-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">Enumerating Musical Notes<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Revisiting Calling Sonic Pi From Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Programming Guitar Greatness<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">Composing Our Own Guitar Amps From Inherited Gear<\/a><\/li><li><strong>Flog-Driven Development<\/strong><\/li><\/ol><h2 id=\"remembering-refactoring\">Remembering Refactoring <a href=\"#remembering-refactoring\">\ud83d\udd17<\/a><\/h2><p>In a <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">prior post<\/a>, we extracted the details of how to tune a guitar out of the <code>Guitar<\/code> class. We <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/#tune-low\">moved it<\/a> to a separate <code>Tuner<\/code> class. In that telling, we did so because it spoke to our sensibilities. It afforded us more space in the <code>Guitar<\/code> class to focus on other responsibilities. It gave a central location to focus on tuning.<\/p><p>What if those justifications weren&rsquo;t enough? What if we needed metrics to give an explanation for our refactoring? Let&rsquo;s use the guidance from a tool called <a href=\"https:\/\/github.com\/seattlerb\/flog\">flog<\/a> to guide our changes.<\/p><h2 id=\"flog\">Flog <a href=\"#flog\">\ud83d\udd17<\/a><\/h2><p>Flog <a href=\"https:\/\/github.com\/seattlerb\/flog#label-DESCRIPTION-3A\">describes itself<\/a> as a tool that:<\/p><blockquote><p>reports the most tortured code in an easy to read pain report. The higher the score, the more pain the code is in.<\/p><\/blockquote><p>You point flog to a file, or directory, and it provides you with a score. The higher the score, the more attention you might want to pay to it. As for how flog calculates the number, I&rsquo;ll let flog <a href=\"https:\/\/ruby.sadi.st\/Flog.html\">summarize itself<\/a> again:<\/p><blockquote><p>Flog essentially scores an ABC metric: Assignments, Branches, Calls, with particular attention placed on calls.<\/p><\/blockquote><p>Let&rsquo;s look at the flog score of our <code>Guitar<\/code> class with all the tuning details inside the class:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog guitar_with_tuning.rb -a<\/span><\/span><span style=\"display:flex;\"><span>65.6: flog total<\/span><\/span><span style=\"display:flex;\"><span>9.4: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>16.8: Guitar#standard_tuning<\/span><\/span><span style=\"display:flex;\"><span>16.8: Guitar#down_half_step_tuning<\/span><\/span><span style=\"display:flex;\"><span>13.0: Guitar#tune<\/span><\/span><span style=\"display:flex;\"><span>6.6: Guitar#pick<\/span><\/span><span style=\"display:flex;\"><span>5.2: Guitar#initialize<\/span><\/span><span style=\"display:flex;\"><span>4.3: Guitar#restring<\/span><\/span><span style=\"display:flex;\"><span>3.0: Guitar#none<\/span><\/span><\/code><\/pre><\/div><p>Let&rsquo;s investigate the method with the biggest flog score.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">standard_tuning<\/span><\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">4<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">3<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:d<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:g<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:b<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">4<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We know that flog is particularly attuned to looking for calls. Each line in this method has a call to access an element in an array, and then another call to the <code>tune<\/code> method. That&rsquo;s two calls per line, with six lines in the method.<\/p><h2 id=\"what-do-we-do-with-the-number\">What do we do with the number? <a href=\"#what-do-we-do-with-the-number\">\ud83d\udd17<\/a><\/h2><p>So, bigger is worse, but how big is bad? At what number should you take action? Thoughtbot&rsquo;s <a href=\"https:\/\/github.com\/thoughtbot\/ruby-science\">Ruby Science<\/a> book suggests a method is long or complex with a flog score above 10. It also posits that a class is long or complex with a flog score above 50.<\/p><p>With those heuristics in mind, our <code>Guitar<\/code> class is long, and all the methods related to tuning are long as well. Let&rsquo;s start by looking at the public method in that class, the method to tune the guitar:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">case<\/span> tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">when<\/span> <span style=\"color:#e6db74\">:standard<\/span><\/span><\/span><span style=\"display:flex;\"><span> standard_tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">when<\/span> <span style=\"color:#e6db74\">:down_half_step<\/span><\/span><\/span><span style=\"display:flex;\"><span> down_half_step_tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">when<\/span> <span style=\"color:#e6db74\">:drop_d<\/span><\/span><\/span><span style=\"display:flex;\"><span> drop_d_tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">else<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#e6db74\">&#34;unknown tuning&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @tuning <span style=\"color:#f92672\">=<\/span> tuning<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The technique I&rsquo;d start with to simplify a complex method is to extract smaller methods from it. However, there doesn&rsquo;t seem to be much gained from that here. This happens to be a really long case statement, each branch of which calls another method.<\/p><p>In an attempt to achieve a lower flog score, I&rsquo;m going to rely on the similar naming of each of these tuning methods. I&rsquo;ll reach for a different tool: metaprogramming.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#e6db74\">&#34;unknown tuning&#34;<\/span> <span style=\"color:#66d9ef\">unless<\/span> <span style=\"color:#66d9ef\">VALID_TUNINGS<\/span><span style=\"color:#f92672\">.<\/span>include?(tuning)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> send(<span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>tuning<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">_tuning&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @tuning <span style=\"color:#f92672\">=<\/span> tuning<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>What does flog think about this change?<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog guitar_tuning_metaprogramming.rb<\/span><\/span><span style=\"display:flex;\"><span>58.1: flog total<\/span><\/span><span style=\"display:flex;\"><span>8.3: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>5.6: Guitar#tune<\/span><\/span><\/code><\/pre><\/div><p>The flog score decreased from 13 to 5.6. A clear improvement - in the eyes of the metric. Is it better though? I&rsquo;d argue that&rsquo;s still a matter of taste. How do you feel about the metaprogramming? How comfortable will you and your team be maintaining this? What pressure does the required naming scheme for any of the tuning methods place on your system? Consider these questions to decide whether to make this change, regardless of the flog score.<\/p><h2 id=\"extract-class\">Extract Class <a href=\"#extract-class\">\ud83d\udd17<\/a><\/h2><p>Even with this change, we still have a class that has a flog score over 50. That puts it in the &ldquo;too long&rdquo; category, according to Ruby Science. So, let&rsquo;s do what we did in the prior post and move everything related to tuning into a <code>Tuner<\/code> class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Tuner<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(guitar)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>); <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">standard_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">down_half_step_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">drop_d_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">open_a_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">modal_c_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">all_fourths_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">all_fifths_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We still need to tune our guitar. We achieve that by delegating this responsibility to our <code>Tuner<\/code> class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Tuner<\/span><span style=\"color:#f92672\">.<\/span>new(self)<span style=\"color:#f92672\">.<\/span>tune(tuning)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>What does that mean for our flog score?<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog guitar_separate_tuner.rb<\/span><\/span><span style=\"display:flex;\"><span>18.9: flog total<\/span><\/span><span style=\"display:flex;\"><span>3.8: flog\/method average<\/span><\/span><\/code><\/pre><\/div><p>Unsurprisingly, deleting most of the code in a class reduces the complexity of that class. We didn&rsquo;t get rid of the complexity though. We just moved it somewhere else. Let&rsquo;s check the flog score of our new <code>Tuner<\/code> class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog tuner.rb<\/span><\/span><span style=\"display:flex;\"><span>58.6: flog total<\/span><\/span><span style=\"display:flex;\"><span>11.7: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>25.2: Tuner#standard_tuning<\/span><\/span><span style=\"display:flex;\"><span>25.2: Tuner#down_half_step_tuning<\/span><\/span><span style=\"display:flex;\"><span>5.3: Tuner#tune<\/span><\/span><\/code><\/pre><\/div><p>The complexity of our tuning methods actually got <em>worse<\/em>, increasing from 16.8 to 25.2. That&rsquo;s because we added another call on each of our lines.<\/p><p>Tuning one string used to look like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>@strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>But now we need to find the string from the guitar that&rsquo;s passed into the tuner.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>@guitar<span style=\"color:#f92672\">.<\/span>strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span>)<\/span><\/span><\/code><\/pre><\/div><h2 id=\"extracting-new-concepts\">Extracting new concepts <a href=\"#extracting-new-concepts\">\ud83d\udd17<\/a><\/h2><p>To handle the complexity, we&rsquo;re going to reshuffle the responsibility of these ideas. Right now the methods for the different tunings tune each string of the guitar. Instead, let&rsquo;s have the tunings only know what notes to tune the strings to.<\/p><p>We&rsquo;ll also extract each of these tunings out to a separate module.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> StandardTuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">pitches<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">4<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:b<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:g<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:d<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> { <span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">octave<\/span>: <span style=\"color:#ae81ff\">2<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now when we want to add a new tuning, we add a new module, rather than another method on the <code>Tuner<\/code> class.<\/p><p>The act of tuning is now isolated into the <code>Tuner#tune<\/code> method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Tuner<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#e6db74\">&#34;unknown tuning&#34;<\/span> <span style=\"color:#66d9ef\">unless<\/span> <span style=\"color:#66d9ef\">VALID_TUNINGS<\/span><span style=\"color:#f92672\">.<\/span>include?(tuning)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> pitches_for(tuning)<span style=\"color:#f92672\">.<\/span>each_with_index <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>pitch, index<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> @guitar<span style=\"color:#f92672\">.<\/span>strings<span style=\"color:#f92672\">[<\/span>index<span style=\"color:#f92672\">].<\/span>tune(<span style=\"color:#f92672\">**<\/span>pitch)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pitches_for<\/span>(tuning)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Object<\/span><span style=\"color:#f92672\">.<\/span>const_get(tuning_class_name(tuning))<span style=\"color:#f92672\">.<\/span>pitches<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tuning_class_name<\/span>(tuning)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>tuning<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">_tuning&#34;<\/span><span style=\"color:#f92672\">.<\/span>split(<span style=\"color:#e6db74\">&#34;_&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:capitalize<\/span>)<span style=\"color:#f92672\">.<\/span>join<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Keeping with our original refactor, this still uses metaprogramming. This time we use it to recall the appropriate module to get the pitches from.<\/p><p>What does flog think about this?<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog tuner_without_tunings.rb<\/span><\/span><span style=\"display:flex;\"><span>21.9: flog total<\/span><\/span><span style=\"display:flex;\"><span>4.4: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>8.5: Tuner#tune<\/span><\/span><span style=\"display:flex;\"><span>6.8: Tuner#tuning_class_name<\/span><\/span><span style=\"display:flex;\"><span>3.6: Tuner#pitches_for<\/span><\/span><\/code><\/pre><\/div><p>Our <code>Tuner<\/code> class has a flog score under 50, and all the methods are under 10. But, like last time, did we just shift the complexity into our extracted tuning modules?<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 flog standard_tuning.rb -a<\/span><\/span><span style=\"display:flex;\"><span>1.5: flog total<\/span><\/span><span style=\"display:flex;\"><span>1.5: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>1.5: StandardTuning::pitches<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>\u21d2 flog down_half_step_tuning.rb -a<\/span><\/span><span style=\"display:flex;\"><span>1.5: flog total<\/span><\/span><span style=\"display:flex;\"><span>1.5: flog\/method average<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>1.5: DownHalfStepTuning::pitches<\/span><\/span><\/code><\/pre><\/div><p>Each of these is lightweight - a single method returning an array of hashes. According to Ruby Science&rsquo;s heuristics, we no longer have complex methods or classes!<\/p><h2 id=\"flog-as-a-forcing-function\">Flog as a forcing function <a href=\"#flog-as-a-forcing-function\">\ud83d\udd17<\/a><\/h2><p>At the time I didn&rsquo;t refer to flog when making these changes. But it was interesting to reconstruct them with a dedicated focus on their flog scores. When you don&rsquo;t have any intuition around the complexity of a piece of code, it can help as an objective source.<\/p><p>Choosing to lean on some of these changes would require more than only the score. Introducing the metaprogramming would depend on my team&rsquo;s attitude towards that language feature.<\/p><p>Those high flog numbers may have driven me to explore different options. I may not have gotten to the point of building separate modules for the tunings. That freed any given class to know about <em>every<\/em> different tuning. Paying attention to the flog score pushed me in ways I may not have otherwise considered.<\/p>"},{"title":"Composing Our Own Guitar Amps From Inherited Gear","link":"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/","pubDate":"Sat, 15 Apr 2023 15:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/","description":"<h2 id=\"anyone-can-play-guitar-series\">Anyone Can Play Guitar Series <a href=\"#anyone-can-play-guitar-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">Enumerating Musical Notes<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Revisiting Calling Sonic Pi From Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Programming Guitar Greatness<\/a><\/li><li><strong>Composing Our Own Guitar Amps From Inherited Gear<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">Flog-Driven Development<\/a><\/li><\/ol><h2 id=\"getting-amped-up\">Getting Amped Up <a href=\"#getting-amped-up\">\ud83d\udd17<\/a><\/h2><p>I <a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/#power-amp\">previously proposed<\/a> a simplified description of an amplifier. They amplify sounds with the help of two components: a pre amp and a power amp. The sound changes as it progresses through the pre amp and power amp. Finally, it projects through the speaker.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">amplify<\/span>(sound)<\/span><\/span><span style=\"display:flex;\"><span> pre_amp_stage(sound)<\/span><\/span><span style=\"display:flex;\"><span> power_amp_stage(sound)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>With this in place, our system can output sound. That&rsquo;d make a pretty short blog post - but we&rsquo;re not done. Guitarists like fiddling with gear. There are many different kinds of amplifiers they may use.<\/p><h2 id=\"tube-amp\">Tube Amp <a href=\"#tube-amp\">\ud83d\udd17<\/a><\/h2><p>A tube amp uses <a href=\"https:\/\/en.wikipedia.org\/wiki\/Vacuum_tube\">vacuum tubes<\/a> to output sound, which is how it gets its name. Guitarists love these amps because of the way they modify the sound of a guitar. The tone that comes out of a tube amp is often described as warm. And as you push the amp to higher volumes, it sounds even warmer.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TubeAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pre_amp_tone<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> low_volume? <span style=\"color:#f92672\">||<\/span> mid_volume?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;\ud83d\udca1&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">elsif<\/span> high_volume?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;\ud83d\udd25&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Unfortunately, these vacuum tubes are relatively <strong>heavy<\/strong> electrical components. It&rsquo;s a good thing they sound so good. So good that a guitarist is willing to risk the health of their back lugging them on and off stage.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">TubeAmp<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>weight<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:heavy<\/span><\/span><\/span><\/code><\/pre><\/div><p>Right now, our amp can change the sound to provide its signature warmth. It can also put you at risk of months of physical therapy. One thing it can&rsquo;t do yet is output that sound. We already built that functionality with our <code>Amplifier<\/code> class. We&rsquo;ll add it into our tube amp by inheriting from the amplifier we&rsquo;ve already built.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TubeAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">TubeAmp<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>respond_to?(<span style=\"color:#e6db74\">:amplify<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"solid-state-amp\">Solid State Amp <a href=\"#solid-state-amp\">\ud83d\udd17<\/a><\/h2><p>Solid state amps have a different tone profile than tube amps. They&rsquo;re described as having a clean sound - as clear as glass. That tone persists no matter how much you push the amp past breakup. It maintains its relative clarity, as opposed to tube amps that get even warmer.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SolidStateAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pre_amp_tone<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;\ud83e\uded9&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>A solid state amp uses <a href=\"https:\/\/en.wikipedia.org\/wiki\/Transistor\">transistors<\/a> to send signals to the speaker. These are much lighter than vacuum tubes, and easier on your back.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">SolidStateAmp<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>weight<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:light<\/span><\/span><\/span><\/code><\/pre><\/div><p>A solid state amp also needs to amplify sound like a tube amp. Let&rsquo;s do the same thing and inherit from the <code>Amplifier<\/code> class to gain that behavior.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SolidStateAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">SolidStateAmp<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>respond_to?(<span style=\"color:#e6db74\">:amplify<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"inheritance\">Inheritance <a href=\"#inheritance\">\ud83d\udd17<\/a><\/h2><p>With both our tube amp and solid state amp, we inherited from an amplifier class. The <code>Amplifier<\/code> has all the behavior that&rsquo;s consistent between different types of amps. By inheriting from it, the amps gain that behavior and structure without having to rewrite it. Those amps then apply their specialization on top of it. Both amps produce a different tone. The different electrical components they use result in different weights.<\/p><p>Inheritance works well here because each of these amplifiers <strong>are<\/strong> still an amplifier. They share the same basic data and behavior, and should continue to have that in common. They have pieces that make their interaction with, or use of, that data or behavior special.<\/p><p>We encode that specialization in that class, and still use the original behavior. <code>Amplifier#amplify<\/code> pushes our sound through its <code>pre_amp_stage<\/code> method. That stage modifies the sound with the amp&rsquo;s <code>pre_amp_tone<\/code>. That allows a tube amp to have its characteristic warmth and a solid state amp to apply its clarity.<\/p><h2 id=\"hybrid-amp\">Hybrid Amp <a href=\"#hybrid-amp\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s build one more type of amp - one that aims to be the best of both worlds. A hybrid amp takes parts of a tube amp and other parts from a solid state amp and combines them.<\/p><p>We want both tube and solid state behavior. Let&rsquo;s attempt the same inheritance approach we&rsquo;ve used so far.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">HybridAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">TubeAmp<\/span>, <span style=\"color:#66d9ef\">SolidStateAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we load up our console we&rsquo;ll see an error about our class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>(irb):<span style=\"color:#ae81ff\">1<\/span>: syntax error, unexpected <span style=\"color:#e6db74\">&#39;,&#39;<\/span>, expecting <span style=\"color:#e6db74\">&#39;;&#39;<\/span> <span style=\"color:#f92672\">or<\/span> <span style=\"color:#e6db74\">&#39;\\n&#39;<\/span> (<span style=\"color:#66d9ef\">SyntaxError<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Ruby doesn&rsquo;t support <a href=\"https:\/\/en.wikipedia.org\/wiki\/Multiple_inheritance\">multiple inheritance<\/a>. We can only have a single class that we inherit from. But we know our <code>HybridAmp<\/code> needs behavior that&rsquo;s the same as each of these other classes. To consider a different approach, let&rsquo;s revisit our definition of an amplifier.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">amplify<\/span>(sound)<\/span><\/span><span style=\"display:flex;\"><span> pre_amp_stage(sound)<\/span><\/span><span style=\"display:flex;\"><span> power_amp_stage(sound)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Each amplifier has a pre amp and a power amp. Let&rsquo;s build separate modules for each of these components&rsquo; behavior.<\/p><p>A hybrid amp has a tube amp pre amp to replicate its warmth.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> TubePreAmp<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pre_amp_tone<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> low_volume? <span style=\"color:#f92672\">||<\/span> mid_volume?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;\ud83d\udca1&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">elsif<\/span> high_volume?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;\ud83d\udd25&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>By including this module in our amp, any <code>HybridAmp<\/code> instances will gain all the behavior in the module.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">HybridAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">TubePreAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">HybridAmp<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">volume<\/span>: <span style=\"color:#ae81ff\">10<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>pre_amp_tone<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;\ud83d\udd25&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>A hybrid amp uses the same power amp as solid state amps to save some weight.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> SolidStatePowerAmp<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">power_amp_weight<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:light<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Unlike inheritance, we can include as many modules as we&rsquo;d like. Let&rsquo;s also include the <code>SolidStatePowerAmp<\/code> behavior in our <code>HybridAmp<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">HybridAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">TubePreAmp<\/span>, <span style=\"color:#66d9ef\">SolidStatePowerAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">HybridAmp<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">volume<\/span>: <span style=\"color:#ae81ff\">10<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>power_amp_weight<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:light<\/span><\/span><\/span><\/code><\/pre><\/div><p>A hybrid amp is still a kind of amplifier, and it still needs the ability to amplify sound. As such, we are <em>also<\/em> going to use inheritance here. Our hybrid amp is a specialization of <code>Amplifier<\/code>, just like a tube or solid state amp. Its specializations come from the modules it includes.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">HybridAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">TubePreAmp<\/span>, <span style=\"color:#66d9ef\">SolidStatePowerAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">HybridAmp<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">volume<\/span>: <span style=\"color:#ae81ff\">10<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> amp<span style=\"color:#f92672\">.<\/span>respond_to?(<span style=\"color:#e6db74\">:amplify<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"composition\">Composition <a href=\"#composition\">\ud83d\udd17<\/a><\/h2><p>We are unable to pull behavior from multiple places with inheritance. We couldn&rsquo;t apply our initial strategy to build our hybrid amp. We didn&rsquo;t want to duplicate the behavior. They are <strong>intentionally<\/strong> the same. We needed a different approach.<\/p><p>We can combine sets of related behavior in modules, and share them between classes. We compose those modules together in our classes to use the behavior they provide.<\/p><p>With this composition approach, our tube and solid state amps become pretty small.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TubeAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">TubePreAmp<\/span>, <span style=\"color:#66d9ef\">TubePowerAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SolidStateAmp<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">SolidStatePreAmp<\/span>, <span style=\"color:#66d9ef\">SolidStatePowerAmp<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The special behavior for each of the amps lives in the modules so that different amp types can use them. We use modules to share that behavior across classes. That allows our hybrid amp to operate the same way as parts of our other amps.<\/p><p>Next let&rsquo;s dig into a <a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">tool<\/a> that can help guide some of the code organization decisions we&rsquo;ve made while building our guitar and amps.<\/p>"},{"title":"Programming Guitar Greatness","link":"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/","pubDate":"Sat, 18 Mar 2023 07:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/","description":"<h2 id=\"anyone-can-play-guitar-series\">Anyone Can Play Guitar Series <a href=\"#anyone-can-play-guitar-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">Enumerating Musical Notes<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Revisiting Calling Sonic Pi From Ruby<\/a><\/li><li><strong>Programming Guitar Greatness<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">Composing Our Own Guitar Amps From Inherited Gear<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">Flog-Driven Development<\/a><\/li><\/ol><blockquote><p>I use heavy strings, tune low, play hard, and floor it. Floor it. That&rsquo;s technical talk.&ndash; <a href=\"https:\/\/twitter.com\/srvofficial\/status\/836589348489424896?lang=en\">Stevie Ray Vaughan<\/a><\/p><\/blockquote><p>Stevie Ray Vaughan is one of my favorite guitarists. Unfortunately, I can&rsquo;t play anything like he can. To make up for it, let&rsquo;s teach a computer to play guitar like him and see what we can learn.<\/p><p>Hit play for some background music or inspiration, and let&rsquo;s get started.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/KC5H9P4F5Uk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"use-heavy-strings\">Use Heavy Strings <a href=\"#use-heavy-strings\">\ud83d\udd17<\/a><\/h2><p>Guitars have many strings (typically six) that you manipulate to make different sounds. To build a system to play guitar, it needs to know about strings. To avoid any <a href=\"https:\/\/ruby-doc.org\/3.1.2\/String.html\">potential confusion<\/a>, we&rsquo;ll build a <code>GuitarString<\/code> class.<\/p><p>Different strings on a guitar are different thicknesses. Thicker strings play notes at a lower frequency than thinner strings. We measure this string thickness in thousands of an inch.<\/p><p>A set of standard strings is a set of nines. The thinnest string in the set is 0.009 inches thick. Stevie Ray Vaughan played a set of 13s that were even thicker on the low end than a stock set of 13s.<\/p><p>These strings are hard to bend, hard to move, and hard to play with. Stevie called them &ldquo;heavy&rdquo;. We&rsquo;ll know if a string is heavy by comparing it to a standard set.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GuitarString<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">heavy?<\/span><\/span><\/span><span style=\"display:flex;\"><span> gauge_number <span style=\"color:#f92672\">&gt;<\/span> common_gauge_number<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h3 id=\"use-domain-terms\">Use Domain Terms <a href=\"#use-domain-terms\">\ud83d\udd17<\/a><\/h3><p>We taught our computer about guitar strings using the language of a guitarist. I would typically refer to a wire as having a particular thickness. Stevie referred to his strings as being &ldquo;heavy&rdquo;. To represent his description, we&rsquo;ll use words that resonate in the world our system models. Our guitar strings are &ldquo;heavy&rdquo;, not &ldquo;thick&rdquo;.<\/p><h2 id=\"tune-low\">Tune Low <a href=\"#tune-low\">\ud83d\udd17<\/a><\/h2><p>We tune each string on a guitar to a note. The most common combination of string tunings is standard tuning. Stevie didn&rsquo;t play in standard tuning. He tuned down a half step. Each string is tuned to a slightly lower pitch than you&rsquo;d regularly expect.<\/p><p>To support this, we need to be able to to tune the guitar. We need to be able to tune the guitar to standard tuning, and down a half step. We&rsquo;ll accept which tuning to use for our guitar as an argument to a <code>tune<\/code> method. We&rsquo;ll switch on that argument to implement that tuning.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">case<\/span> tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">when<\/span> <span style=\"color:#e6db74\">:standard<\/span><\/span><\/span><span style=\"display:flex;\"><span> standard_tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">when<\/span> <span style=\"color:#e6db74\">:down_half_step<\/span><\/span><\/span><span style=\"display:flex;\"><span> down_half_step_tuning<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>These aren&rsquo;t the only tunings possible for a guitar. There are a <strong>lot<\/strong> of them. Supporting more will mean this method gets more complex. Our guitar class gets more complex.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>); <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">standard_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">down_half_step_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">drop_d_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">open_a_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">modal_c_tuning<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>As we handle more and more tunings, our guitar grows in complexity. It starts to look like an instrument primarily responsible for tuning itself. The number of methods that have to do with tuning takes away from the more exciting things you can do with a guitar.<\/p><p>To focus on a guitar&rsquo;s other responsibilities, let&rsquo;s extract this logic. A <code>Tuner<\/code> class will take a guitar as a dependency and know how to tune it to a variety of tunings. That changes the implementation in our <code>Guitar<\/code> class to look like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">tune<\/span>(tuning <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:standard<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Tuner<\/span><span style=\"color:#f92672\">.<\/span>new(self)<span style=\"color:#f92672\">.<\/span>tune(tuning)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>All the complexity of different guitar tunings is still in our system. It&rsquo;s in the <code>Tuner<\/code> class now, not <code>Guitar<\/code>.<\/p><h3 id=\"extract-related-behavior\">Extract Related Behavior <a href=\"#extract-related-behavior\">\ud83d\udd17<\/a><\/h3><p>In a system responsible for playing the guitar, that class attracts a lot of behavior. That can make understanding all the responsibilities that the class has difficult. When we can identify a set of related behaviors, we should explore moving it into a separate class.<\/p><p>An early indicator of this can be when related methods are physically grouped together in a class. Especially when those methods don&rsquo;t have anything to do with other parts of a class. Our guitar had a lot of different methods for each of the different tunings. We moved those out of the class and into another that&rsquo;s responsible for all possible tunings.<\/p><p>Even if this class isn&rsquo;t reused or composed in other classes, there&rsquo;s value here. We&rsquo;ve freed up complexity inside the <code>Guitar<\/code> class, while not taking away any of its ability. We can still tune the guitar to any number of tunings. We don&rsquo;t need to worry ourselves with the implementation details of <em>how<\/em> most of the time. And when we do need to dig into how a guitar gets tuned, we know where to look. We don&rsquo;t need to dig into the depths of various private methods in <code>Guitar<\/code>. We can start with our aptly-named <code>Tuner<\/code> class.<\/p><h2 id=\"play-hard\">Play Hard <a href=\"#play-hard\">\ud83d\udd17<\/a><\/h2><p>We make sounds on our guitar by plucking the strings with one hand. The other hand presses down on the strings on the neck of the guitar. The neck has many sections called frets. Pressing down on each of these plays a higher frequency as you move up the neck towards your other hand.<\/p><p>Our <code>GuitarString<\/code> knows which note we play, based on what note it&rsquo;s tuned to and which fret our hand is on. But we don&rsquo;t say we play the guitar strings - we play the guitar.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pick<\/span>(<span style=\"color:#e6db74\">string<\/span>:, <span style=\"color:#e6db74\">fret<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @strings<span style=\"color:#f92672\">[<\/span>string <span style=\"color:#f92672\">-<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: fret)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>With this implementation, we get back the note of the sound the guitar makes.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>guitar <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Guitar<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>guitar<span style=\"color:#f92672\">.<\/span>tune<\/span><\/span><span style=\"display:flex;\"><span>guitar<span style=\"color:#f92672\">.<\/span>pick(<span style=\"color:#e6db74\">string<\/span>: <span style=\"color:#ae81ff\">6<\/span>, <span style=\"color:#e6db74\">fret<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:f<\/span><\/span><\/span><\/code><\/pre><\/div><h3 id=\"compose-collaborators\">Compose Collaborators <a href=\"#compose-collaborators\">\ud83d\udd17<\/a><\/h3><p>Much like with our tuning, our public interface is through the guitar. Again, the majority of our work isn&rsquo;t done by the <code>Guitar<\/code> class. Its responsibility is taking the input and passing it off to a collaborating class. Here, it figures out which of the strings is being played, and sends it the <code>pluck<\/code> method.<\/p><p>The <code>GuitarString<\/code> handles the hard work of which musical note comes out of the guitar. The <code>Guitar<\/code> knows how to work with its strings to achieve the result that the caller asked for.<\/p><h2 id=\"floor-it\">Floor It <a href=\"#floor-it\">\ud83d\udd17<\/a><\/h2><p>It&rsquo;s not enough to know which note we&rsquo;re playing. It&rsquo;s a start, but doesn&rsquo;t tell the full story about how what we play sounds. We also need to know which <em>octave<\/em> of the note we&rsquo;re playing.<\/p><p>We&rsquo;ll change our <code>GuitarString<\/code> class to return not only the note, but also the octave. We return both elements in an <a href=\"https:\/\/ruby-doc.org\/3.1.2\/Array.html\">array<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GuitarString<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pluck<\/span>(<span style=\"color:#e6db74\">fret<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span>note, octave<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now let&rsquo;s display all this data.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>note <span style=\"color:#f92672\">=<\/span> guitar<span style=\"color:#f92672\">.<\/span>pick(hand_position)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>note<span style=\"color:#f92672\">.<\/span>first<span style=\"color:#e6db74\">}#{<\/span>note<span style=\"color:#f92672\">.<\/span>last<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>We know that the first element is the note, and the last element is the octave. We know that because we just wrote it, and it&rsquo;s sitting right above our use of it. However, it&rsquo;s not obvious in other cases what each of these elements refers to.<\/p><p>We&rsquo;ll address that by building a <a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">custom Note class<\/a> and returning it in <code>GuitarString#pluck<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">GuitarString<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pluck<\/span>(<span style=\"color:#e6db74\">fret<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">starting_note<\/span>: @tuning_note,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">starting_octave<\/span>: @tuning_octave,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">offset<\/span>: fret,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>That will change our display logic.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>note <span style=\"color:#f92672\">=<\/span> guitar<span style=\"color:#f92672\">.<\/span>pick(hand_position)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>note<span style=\"color:#f92672\">.<\/span>value<span style=\"color:#e6db74\">}#{<\/span>note<span style=\"color:#f92672\">.<\/span>octave<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now it&rsquo;s clear what the data is that we&rsquo;re displaying. It&rsquo;s not the first element, it&rsquo;s the note value. It&rsquo;s not the last element, it&rsquo;s the note octave.<\/p><p>We could achieve a similar result by using a <a href=\"https:\/\/ruby-doc.org\/3.1.2\/Hash.html\">Hash<\/a>. That will allow us to name the data elements we refer to in our display logic.<\/p><p>By moving to a separate class, we can also associate behavior with this data.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">value<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">octave<\/span>; <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">to_s<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>value<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">].<\/span>upcase<span style=\"color:#e6db74\">}#{<\/span><span style=\"color:#e6db74\">&#39;b&#39;<\/span> <span style=\"color:#66d9ef\">if<\/span> flat?<span style=\"color:#e6db74\">}#{<\/span>octave<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now our display logic doesn&rsquo;t even need to know about the internals of the <code>Note<\/code> class. Instead, it just needs to ask it to display itself.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>guitar<span style=\"color:#f92672\">.<\/span>pick(hand_position)<span style=\"color:#f92672\">.<\/span>to_s<\/span><\/span><\/code><\/pre><\/div><h3 id=\"elevate-primitives-to-objects\">Elevate Primitives to Objects <a href=\"#elevate-primitives-to-objects\">\ud83d\udd17<\/a><\/h3><p>We needed to return a collection of information out of our <code>GuitarString#pluck<\/code> method. Callers need both the note and octave to know what our output sounds like. We started with a primitive data structure from Ruby, an Array.<\/p><p>That worked, but wasn&rsquo;t very clear what all the data elements represented. We can bring clarity to that with another primitive, a Hash. Later on, we wanted to exercise custom behavior on top of this collection of data. To do that, we made a separate class that encapsulates this data <strong>and<\/strong> related behavior.<\/p><h2 id=\"thats-technical-talk\">That&rsquo;s Technical Talk <a href=\"#thats-technical-talk\">\ud83d\udd17<\/a><\/h2><p>We now have a system that knows how to play guitar <em>just like<\/em> Stevie Ray Vaughan did. Except for all the talent, the feeling, the creativity, and the humanity that went into his playing.<\/p><p>Along the way, we reinforced concepts by using domain terminology. We identified related behavior within a class and extracted it to a separate class. We collaborated with those extractions to build up our system. Our public interfaces (like the <code>Guitar#pick<\/code> method) don&rsquo;t need to house the complexity. And we built more classes to replace primitive data structures. When we identified behavior related to that data, we had a natural landing place for it.<\/p><p>I hope this Texas-sized flood of information helps in your next domain modeling exercise.<\/p><p>We can go a step further. Now that we know how to play guitar, let&rsquo;s <a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">build some amps<\/a> so people can hear us play.<\/p>"},{"title":"Speaking at Blue Ridge Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023\/","pubDate":"Wed, 15 Mar 2023 17:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/blue-ridge-ruby-2023\/","description":"<p>I&rsquo;m thrilled to be part of the <a href=\"https:\/\/blueridgeruby.com\/#program\">program<\/a> for Blue Ridge Ruby 2023 in Asheville, North Carolina.<\/p><p>I&rsquo;ll be <a href=\"https:\/\/blueridgeruby.com\/speakers\/kevin-murphy\/\">speaking<\/a> about Ruby&rsquo;s Coverage module. I hope you can join us!<\/p>"},{"title":"Duped into modifying a frozen hash","link":"https:\/\/kevinjmurphy.com\/posts\/modify-frozen-hash\/","pubDate":"Fri, 17 Feb 2023 19:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/modify-frozen-hash\/","description":"<h2 id=\"flash-freeze\">Flash Freeze <a href=\"#flash-freeze\">\ud83d\udd17<\/a><\/h2><p>You can freeze a hash to prevent modifying its contents. The effect of freezing the hash is only one level deep. The values in the hash aren&rsquo;t frozen. As expected, I can&rsquo;t change the entire value of a key.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">c<\/span>: <span style=\"color:#e6db74\">&#34;three&#34;<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;four&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>(irb):<span style=\"color:#ae81ff\">13<\/span><span style=\"color:#e6db74\">:in<\/span> <span style=\"color:#e6db74\">`&lt;main&gt;&#39;: can&#39;t modify frozen Hash: {:c=&gt;&#34;three&#34;} (FrozenError)<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, I can modify a string value in the frozen hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">c<\/span>: <span style=\"color:#e6db74\">&#34;three&#34;<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">].<\/span>upcase!<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;THREE&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;THREE&#34;<\/span>}<\/span><\/span><\/code><\/pre><\/div><p>I can also modify a hash inside the frozen hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: { <span style=\"color:#e6db74\">b<\/span>: <span style=\"color:#ae81ff\">2<\/span> } }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">2<\/span>}}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">2<\/span>}}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">][<\/span><span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">4<\/span>}}<\/span><\/span><\/code><\/pre><\/div><p>And modify an array inside the frozen hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">d<\/span>: <span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span>, <span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">]<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:d<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span>, <span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">]<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:d<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span>, <span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">]<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:d<\/span><span style=\"color:#f92672\">].<\/span>map!(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:even?<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#66d9ef\">false<\/span>, <span style=\"color:#66d9ef\">true<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:d<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#66d9ef\">false<\/span>, <span style=\"color:#66d9ef\">true<\/span><span style=\"color:#f92672\">]<\/span>}<\/span><\/span><\/code><\/pre><\/div><p>I can modify an instance of an object inside a frozen hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Alice&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#75715e\">#&lt;User:0x000000010b50f8c8 @first_name=&#34;Alice&#34;&gt;}<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#75715e\">#&lt;User:0x000000010b50f8c8 @first_name=&#34;Alice&#34;&gt;}<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">].<\/span>first_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Carol&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Carol&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#75715e\">#&lt;User:0x000000010b50f8c8 @first_name=&#34;Carol&#34;&gt;}<\/span><\/span><\/span><\/code><\/pre><\/div><p>The immutability isn&rsquo;t &ldquo;deep&rdquo;. It&rsquo;s shallow. It doesn&rsquo;t nest down to lower levels. Adding deep freezing to Ruby itself has been <a href=\"https:\/\/bugs.ruby-lang.org\/issues\/2509\">discussed<\/a>. There are also gems that you can use to recursively freeze objects.<\/p><p>The affect of freeze only being shallow isn&rsquo;t specific to a hash. It applies to other data structures and objects as well. However, I specifically want to talk about hashes because&hellip;<\/p><h2 id=\"frozen-rails-shot\">Frozen Rail(s) Shot <a href=\"#frozen-rails-shot\">\ud83d\udd17<\/a><\/h2><p>Rails includes a <code>HashWithIndifferentAccess<\/code> <a href=\"https:\/\/api.rubyonrails.org\/classes\/ActiveSupport\/HashWithIndifferentAccess.html\">class<\/a>. That class considers the keys <code>:a<\/code> and <code>&quot;a&quot;<\/code> to be the same key. It&rsquo;s part of <code>ActiveSupport<\/code>, and that also includes a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/activesupport\/lib\/active_support\/core_ext\/hash\/indifferent_access.rb#L9-L11\">core extension<\/a> to the hash class. That lets you call <code>.with_indifferent_access<\/code> on a hash to transform it into a hash with indifferent access.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Hash<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">with_indifferent_access<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ActiveSupport<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">HashWithIndifferentAccess<\/span><span style=\"color:#f92672\">.<\/span>new(self)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can use a <code>HashWithIndifferentAccess<\/code> like this.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#ae81ff\">1<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">nil<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>with_indifferent_access<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">005<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">006<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><\/code><\/pre><\/div><p>You can also freeze a hash with indifferent access. It&rsquo;s like freezing a hash in the ways described above. It still only applies one level deep.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> h <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">c<\/span>: <span style=\"color:#e6db74\">&#34;three&#34;<\/span> }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>with_indifferent_access<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;c&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> i<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;c&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;four&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>can<span style=\"color:#960050;background-color:#1e0010\">&#39;<\/span>t modify frozen <span style=\"color:#66d9ef\">ActiveSupport<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#e6db74\">HashWithIndifferentAccess<\/span>: {<span style=\"color:#e6db74\">&#34;c&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;three&#34;<\/span>} (<span style=\"color:#66d9ef\">FrozenError<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">005<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span><span style=\"color:#f92672\">].<\/span>upcase!<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;THREE&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">006<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;c&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;THREE&#34;<\/span>}<\/span><\/span><\/code><\/pre><\/div><h2 id=\"the-cold-never-bothered-me-anyway\">The Cold Never Bothered Me Anyway <a href=\"#the-cold-never-bothered-me-anyway\">\ud83d\udd17<\/a><\/h2><p><code>HashWithIndifferentAccess<\/code> provides another way you can accidentally allow changes to a frozen hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#ae81ff\">1<\/span> }<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span>(irb):<span style=\"color:#ae81ff\">2<\/span><span style=\"color:#e6db74\">:in<\/span> <span style=\"color:#f92672\">&lt;<\/span>main<span style=\"color:#f92672\">&gt;<\/span><span style=\"color:#e6db74\">&#39;: can&#39;<\/span>t modify frozen <span style=\"color:#e6db74\">Hash<\/span>: {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>} (<span style=\"color:#66d9ef\">FrozenError<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent <span style=\"color:#f92672\">=<\/span> frozen<span style=\"color:#f92672\">.<\/span>with_indifferent_access<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">005<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">2<\/span>}<\/span><\/span><\/code><\/pre><\/div><p>Here I created a frozen hash and could not modify it. I then called <code>with_indifferent_access<\/code> on it. The resulting <code>HashWithIndifferentAccess<\/code> <em>could<\/em> change - even with the original hash frozen.<\/p><h2 id=\"brain-freeze\">Brain Freeze <a href=\"#brain-freeze\">\ud83d\udd17<\/a><\/h2><p>Originally I guessed that maybe <code>HashWithIndifferentAccess<\/code> is using the <code>dup<\/code> <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Object.html#method-i-dup\">method<\/a>. That does not preserve the frozen status of what it&rsquo;s duplicating.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#ae81ff\">1<\/span> }<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> duplicated <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>dup<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> duplicated<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><\/code><\/pre><\/div><p>As an aside, <code>clone<\/code> <em>will<\/em> preserve the frozen status.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#ae81ff\">1<\/span> }<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> cloned <span style=\"color:#f92672\">=<\/span> h<span style=\"color:#f92672\">.<\/span>clone<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> cloned<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, my intuition was wrong. <code>dup<\/code> doesn&rsquo;t play a role in the source code. The constructor for <code>HashWithIndifferentAccess<\/code> takes an argument. In our case it&rsquo;s our original frozen hash. It will <a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/activesupport\/lib\/active_support\/hash_with_indifferent_access.rb#L71\">pass that argument<\/a> to its <code>update<\/code> method.<\/p><p>That will eventually <a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/activesupport\/lib\/active_support\/hash_with_indifferent_access.rb#L412-L417\">iterate<\/a> through each key, value pair in the original hash and write it to the <code>HashWithIndifferentAccess<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>other_hash<span style=\"color:#f92672\">.<\/span>to_hash<span style=\"color:#f92672\">.<\/span>each_pair <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>key, value<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> block <span style=\"color:#f92672\">&amp;&amp;<\/span> key?(key)<\/span><\/span><span style=\"display:flex;\"><span> value <span style=\"color:#f92672\">=<\/span> block<span style=\"color:#f92672\">.<\/span>call(convert_key(key), self<span style=\"color:#f92672\">[<\/span>key<span style=\"color:#f92672\">]<\/span>, value)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> regular_writer(convert_key(key), convert_value(value))<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The <code>regular_writer<\/code> method is an <a href=\"https:\/\/github.com\/rails\/rails\/blob\/7-0-stable\/activesupport\/lib\/active_support\/hash_with_indifferent_access.rb#L87\">alias<\/a> for <code>[]=<\/code>.<\/p><p><code>HashWithIndifferentAccess<\/code> is also indifferent about the original hash&rsquo;s frozen status. It constructs a new hash, or hash-like object, setting its keys and values based on the original hash. Whether that original hash is frozen or not doesn&rsquo;t matter. The new hash-like object is not.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#ae81ff\">1<\/span> }<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen<span style=\"color:#f92672\">.<\/span>with_indifferent_access<span style=\"color:#f92672\">.<\/span>frozen?<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><\/code><\/pre><\/div><p>The frozen status of the <em>values<\/em> do carry over though.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> frozen <span style=\"color:#f92672\">=<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#e6db74\">&#34;not changing&#34;<\/span><span style=\"color:#f92672\">.<\/span>freeze }<span style=\"color:#f92672\">.<\/span>freeze<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;not changing&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent <span style=\"color:#f92672\">=<\/span> frozen<span style=\"color:#f92672\">.<\/span>with_indifferent_access<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;a&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><span style=\"color:#e6db74\">&#34;not changing&#34;<\/span>}<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> indifferent<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:a<\/span><span style=\"color:#f92672\">].<\/span>upcase!<\/span><\/span><span style=\"display:flex;\"><span>(irb):<span style=\"color:#ae81ff\">8<\/span><span style=\"color:#e6db74\">:in<\/span> <span style=\"color:#e6db74\">`upcase!&#39;: can&#39;t modify frozen String: &#34;not changing&#34; (FrozenError)<\/span><\/span><\/span><\/code><\/pre><\/div><p>The string in the value of <code>:a<\/code> is still frozen, even after making it a hash with indifferent access.<\/p><h2 id=\"things-got-real-quiet-real-fast-tenth-avenue-freeze-out\">Things Got Real Quiet Real Fast (Tenth Avenue Freeze-out) <a href=\"#things-got-real-quiet-real-fast-tenth-avenue-freeze-out\">\ud83d\udd17<\/a><\/h2><p>Calling <code>freeze<\/code> on an object does give you certain (limited) immutability guarantees. Be particularly mindful of how you&rsquo;re interacting with a frozen object. In my case, calling <code>h.freeze.with_indifferent_access<\/code> left me <em>thinking<\/em> I was working with a frozen hash with indifferent access. I was wrong. Flipping the order, and calling <code>h.with_indifferent_access.freeze<\/code> <em>does<\/em> give me what I was expecting.<\/p><p>Stay cool!<\/p>"},{"title":"Ruby on Rails Podcast Appearance","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-appearance\/","pubDate":"Wed, 08 Feb 2023 18:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-on-rails-podcast-appearance\/","description":"<h2 id=\"the-ruby-on-rails-podcast\">The Ruby on Rails Podcast <a href=\"#the-ruby-on-rails-podcast\">\ud83d\udd17<\/a><\/h2><p>I was a guest on <a href=\"https:\/\/www.therubyonrailspodcast.com\/456\">episode 456<\/a> of The Ruby on Rails Podcast. We talked about BookBub, coverage, contributing to Ruby, and conference speaking.<\/p><p>I&rsquo;m really thankful Brittany Martin asked me to come on. It&rsquo;s a big honor for me to have appeared on the show.<\/p><iframe src=\"https:\/\/player.fireside.fm\/v2\/3OC19MC9+n3dxA1IW?theme=dark\" width=\"740\" height=\"200\" frameborder=\"0\" scrolling=\"no\"><\/iframe>"},{"title":"Anyone Can Play Guitar (With Ruby) Video Released","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-video-released\/","pubDate":"Mon, 06 Feb 2023 18:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-video-released\/","description":"<h2 id=\"rubyconf-2022-videos-released\">RubyConf 2022 Videos Released <a href=\"#rubyconf-2022-videos-released\">\ud83d\udd17<\/a><\/h2><p>RubyCentral has released the videos from RubyConf and RubyConf Mini 2022 ontheir YouTube page. That includes my talk, <a href=\"https:\/\/youtu.be\/t0C75Z0LX20\">&ldquo;Anyone Can Play Guitar (With Ruby)&rdquo;<\/a>.<\/p><p>My slides, proposal, code examples, and related blog posts are <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">available to accompany the talk<\/a>.<\/p><p>If you&rsquo;re curious for other talks to check out, you can see my <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/\">RubyConf Mini recap<\/a> for some ideas.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/t0C75Z0LX20\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe>"},{"title":"Evaluating More Coverage in Ruby 3.2","link":"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/","pubDate":"Sat, 14 Jan 2023 16:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/","description":"<h2 id=\"measuring-coverage-of-eval\">Measuring Coverage of Eval <a href=\"#measuring-coverage-of-eval\">\ud83d\udd17<\/a><\/h2><p>As I mentioned in my <a href=\"https:\/\/kevinjmurphy.com\/posts\/my-first-code-commit-in-ruby\/\">prior post<\/a>, Ruby 3.2.0 has some changes to the <code>Coverage<\/code> module. Now the module can measure the coverage of a Ruby expression in a string passed to the <a href=\"https:\/\/ruby-doc.org\/3.2.0\/Kernel.html#method-i-eval\">eval<\/a> method.<\/p><p>This is important because of templates. ERB, when we ask for the template through the <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/ERB.html#method-i-result\">result method<\/a>, calls <code>eval<\/code>. When Rails is rendering a view, that <a href=\"https:\/\/github.com\/rails\/rails\/blob\/2f36f0a2bb9d6244035f37e62c978ac11ef88411\/actionview\/lib\/action_view\/template.rb#L368-L372\">also calls<\/a> <code>eval<\/code>. More specifically, Rails calls the <code>module_eval<\/code> <a href=\"https:\/\/ruby-doc.org\/3.2.0\/Module.html#method-i-module_eval\">method<\/a>.<\/p><p>Have you wondered how much of the logic in your views is exercised in your test suite? Thanks to this change, now you can see that in tools like <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/pull\/1037\">SimpleCov<\/a>.<\/p><h2 id=\"feature-introduction\">Feature Introduction <a href=\"#feature-introduction\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s walk through an example demonstrating this functionality.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(eval: <span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#e6db74\">lines<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>eval(<span style=\"color:#e6db74\">&#34;1 &gt; 2 ? &#39;not reached&#39; : &#39;covered&#39;&#34;<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span>, <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:lines<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">]<\/span>}, <span style=\"color:#e6db74\">&#34;(irb)&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:lines<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">]<\/span>}}<\/span><\/span><\/code><\/pre><\/div><p>We need to require the Coverage module first. After that, we ask coverage to start measuring with the <code>start<\/code> method. Here we explicitly ask it to measure <code>eval<\/code>. We&rsquo;re using lines coverage to answer how many times each line is run.<\/p><p>We call <code>eval<\/code>, passing a string with a ternary statement. We also pass in the optional filename and line number parameters as well. We check our measurement with the <code>result<\/code> method.<\/p><p>The keys of that hash are file names, or places, where Ruby measures coverage. Because we passed in the filename parameter in our <code>eval<\/code> call, the <code>eval<\/code> coverage has a key of the filename passed to <code>eval<\/code>.<\/p><p>Lines coverage provides an array of numbers. Each number tells us how many times each line was executed. The first item in the array, at index 0, is how many times the first line was executed. Here we see our first line of our single-line <code>eval<\/code> statement was executed once, as we&rsquo;d expect.<\/p><h2 id=\"opting-in-to-eval\">Opting in to eval <a href=\"#opting-in-to-eval\">\ud83d\udd17<\/a><\/h2><p>In Ruby 3.2.0, measuring coverage of <code>eval<\/code> statements is optional. By default,coverage will <em>not<\/em> measure <code>eval<\/code> coverage. You must explicitly tell it to bypassing <code>eval: true<\/code> to <code>Coverage.start<\/code>. Notice how we have no coverage resultswithout passing <code>eval: true<\/code>. That&rsquo;s because otherwise, Coverage is looking tomeasure loaded files.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">lines<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>eval(<span style=\"color:#e6db74\">&#34;1 &gt; 2 ? &#39;not reached&#39; : &#39;covered&#39;&#34;<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span>, <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {}<\/span><\/span><\/code><\/pre><\/div><p>Using the <code>:all<\/code> option will also measure the coverage of <code>eval<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">:all<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> eval(<span style=\"color:#e6db74\">&#34;1 &gt; 2 ? &#39;not reached&#39; : &#39;covered&#39;&#34;<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span>, <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">004<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span>{<span style=\"color:#e6db74\">&#34;(irb)&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:lines<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">]<\/span>, <span style=\"color:#e6db74\">:branches<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}, <span style=\"color:#e6db74\">:methods<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">:lines<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">]<\/span>, <span style=\"color:#e6db74\">:branches<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:if<\/span>, <span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">33<\/span><span style=\"color:#f92672\">]=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:then<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">8<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">21<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:else<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">24<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">33<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}}, <span style=\"color:#e6db74\">:methods<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}}}<\/span><\/span><\/code><\/pre><\/div><h2 id=\"setting-the-mode\">Setting the Mode <a href=\"#setting-the-mode\">\ud83d\udd17<\/a><\/h2><p>I have unintentionally demonstrated this when showing the <code>:all<\/code> option, but youcan measure coverage of <code>eval<\/code> statements with the <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/#coverage-modes\">different modes available<\/a> in Coverage.<\/p><p>Our <code>eval<\/code> statement has two code paths on a single line because it&rsquo;s a ternary.Let&rsquo;s measure the branches coverage of our statement.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(eval: <span style=\"color:#66d9ef\">true<\/span>, <span style=\"color:#e6db74\">branches<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>eval(<span style=\"color:#e6db74\">&#34;1 &gt; 2 ? &#39;not reached&#39; : &#39;covered&#39;&#34;<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span>, <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:branches<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:if<\/span>, <span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">33<\/span><span style=\"color:#f92672\">]=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:then<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">8<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">21<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:else<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">24<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">33<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">1<\/span>}}}, <span style=\"color:#e6db74\">&#34;(irb)&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:branches<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}}}<\/span><\/span><\/code><\/pre><\/div><p>Here we see that we&rsquo;ve executed the <code>else<\/code> statement of our if test, but not the other side (the <code>then<\/code>) of our conditional. A more in-depth explanation of this output is available <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/#branches-coverage\">here<\/a>.<\/p><h2 id=\"mode-required\">Mode Required <a href=\"#mode-required\">\ud83d\udd17<\/a><\/h2><p><em>Generally<\/em>, Coverage will start in lines mode when provided no options in <code>start<\/code>. However, I&rsquo;ve noticed that when you ask Coverage to start and measure eval coverage, you <strong>must<\/strong> also specify the mode(s) you want to measure.<\/p><p>As you can see, starting Coverage with eval on and no mode gives us no coverageresults.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(eval: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>eval(<span style=\"color:#e6db74\">&#34;1 &gt; 2 ? &#39;not reached&#39; : &#39;covered&#39;&#34;<\/span>, <span style=\"color:#66d9ef\">nil<\/span>, <span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span>, <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">&#34;filename.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}, <span style=\"color:#e6db74\">&#34;(irb)&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{}}<\/span><\/span><\/code><\/pre><\/div><h2 id=\"credit\">Credit <a href=\"#credit\">\ud83d\udd17<\/a><\/h2><p>Thank you to <a href=\"https:\/\/github.com\/ioquatix\">Samuel Williams<\/a> for introducing this functionality into the Rubycodebase. Thank you to <a href=\"https:\/\/github.com\/mame\">Yusuke Endoh<\/a> for adding it to SimpleCov, and also forwriting and maintaining <em>most<\/em> of the Coverage functionality available in Ruby&rsquo;sstandard library.<\/p>"},{"title":"My First Code Commit in Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/my-first-code-commit-in-ruby\/","pubDate":"Thu, 29 Dec 2022 12:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/my-first-code-commit-in-ruby\/","description":"<h2 id=\"kevin-murphy-ruby-committer\">Kevin Murphy: Ruby Committer <a href=\"#kevin-murphy-ruby-committer\">\ud83d\udd17<\/a><\/h2><p>I recently had my first <a href=\"https:\/\/github.com\/ruby\/ruby\/commit\/b3d330c39ebbf27cefc2d83109dad9e0b3b0e94f\">code change<\/a> accepted to the Ruby programming language! Ruby&rsquo;s <code>Coverage<\/code> module now responds that it supports oneshot_lines mode.<\/p><p>Though it may be a small change, I&rsquo;m more excited at the symbolism of having committed to the Ruby code base. As a celebration, I figured I&rsquo;d share what led to this.<\/p><h2 id=\"covering-new-changes\">Covering New Changes <a href=\"#covering-new-changes\">\ud83d\udd17<\/a><\/h2><p>As I write this, we&rsquo;re in that period between Christmas and New Year&rsquo;s Day. In the past few years, I&rsquo;ve taken that off of work. That also lines up with the release day for a new version of Ruby. This year, <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2022\/12\/25\/ruby-3-2-0-released\/\">3.2.0<\/a> came out on Christmas day.<\/p><p>Having some time where I&rsquo;m not working, I set some very healthy boundaries. Instead of working, I would instead <em>read<\/em> about work-related things. While I was catching up on the changes in 3.2.0, something caught my eye - there were some changes to Ruby&rsquo;s Coverage module.<\/p><p>Coverage can <a href=\"https:\/\/github.com\/ruby\/ruby\/pull\/6396\">now support<\/a> measuring code coverage of code in <code>eval<\/code>. I&rsquo;ve written more about that in a <a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/\">later post<\/a>. As a follow-up to conversation in the <a href=\"https:\/\/bugs.ruby-lang.org\/issues\/19008\">initial issue<\/a>, <a href=\"https:\/\/github.com\/ruby\/ruby\/pull\/6462\">another PR<\/a> adds controls for whether <code>eval<\/code> coverage is running.<\/p><p>Part of that change adds a <a href=\"https:\/\/bugs.ruby-lang.org\/issues\/19026\">new method<\/a> to the <code>Coverage<\/code> API. <code>Coverage.supported?<\/code> is a method that accepts a symbol as an argument. It tells you if the Coverage module supports the mode or type of coverage asked about.<\/p><p>I pulled up the <a href=\"https:\/\/ruby-doc.org\/3.2.0\/exts\/coverage\/Coverage.html#method-c-supported-3F\">documentation<\/a> for the method, and noticed this sentence:<\/p><blockquote><p>The mode should be one of the following symbols: :lines, :branches, :methods, :eval.<\/p><\/blockquote><p>I also happen to know another mode of coverage: <a href=\"https:\/\/ruby-doc.org\/3.2.0\/exts\/coverage\/Coverage.html#module-Coverage-label-Oneshot+Lines+Coverage\">oneshot lines<\/a>. More on why I know about that later. Wondering if there was an opportunity to update the documentation, I checked the <a href=\"https:\/\/github.com\/ruby\/ruby\/blob\/a7d467a792c644a7260d6560ea2002fdb8ff6de3\/ext\/coverage\/coverage.c#L40\">source code<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-c\" data-lang=\"c\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">static<\/span> VALUE<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#a6e22e\">rb_coverage_supported<\/span>(VALUE self, VALUE _mode)<\/span><\/span><span style=\"display:flex;\"><span>{<\/span><\/span><span style=\"display:flex;\"><span> ID mode <span style=\"color:#f92672\">=<\/span> <span style=\"color:#a6e22e\">RB_SYM2ID<\/span>(_mode);<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> <span style=\"color:#a6e22e\">RBOOL<\/span>(<\/span><\/span><span style=\"display:flex;\"><span> mode <span style=\"color:#f92672\">==<\/span> <span style=\"color:#a6e22e\">rb_intern<\/span>(<span style=\"color:#e6db74\">&#34;lines&#34;<\/span>) <span style=\"color:#f92672\">||<\/span><\/span><\/span><span style=\"display:flex;\"><span> mode <span style=\"color:#f92672\">==<\/span> <span style=\"color:#a6e22e\">rb_intern<\/span>(<span style=\"color:#e6db74\">&#34;branches&#34;<\/span>) <span style=\"color:#f92672\">||<\/span><\/span><\/span><span style=\"display:flex;\"><span> mode <span style=\"color:#f92672\">==<\/span> <span style=\"color:#a6e22e\">rb_intern<\/span>(<span style=\"color:#e6db74\">&#34;methods&#34;<\/span>) <span style=\"color:#f92672\">||<\/span><\/span><\/span><span style=\"display:flex;\"><span> mode <span style=\"color:#f92672\">==<\/span> <span style=\"color:#a6e22e\">rb_intern<\/span>(<span style=\"color:#e6db74\">&#34;eval&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> );<\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>It turns out, the source doesn&rsquo;t mention oneshot lines either. I asked Samuel, who made these contributions, on <a href=\"https:\/\/ruby.social\/@kevin_j_m\/109591612453458271\">Mastodon<\/a> about it. After that, I wrote my <a href=\"https:\/\/bugs.ruby-lang.org\/issues\/19279\">first issue<\/a> on Ruby&rsquo;s issue tracker, and had <a href=\"https:\/\/github.com\/ruby\/ruby\/pull\/7040\">my PR<\/a> merged in.<\/p><p>So how do I know about oneshot lines coverage? Or Ruby&rsquo;s Coverage module at all? To answer that, we need to go back much further in time.<\/p><h2 id=\"covering-my-history-with-coverage\">Covering My History With Coverage <a href=\"#covering-my-history-with-coverage\">\ud83d\udd17<\/a><\/h2><h3 id=\"rubyconf-2019\">RubyConf 2019 <a href=\"#rubyconf-2019\">\ud83d\udd17<\/a><\/h3><p>The year is 2019. I&rsquo;m speaking at RubyConf for the first time. I&rsquo;m very excited about this. It&rsquo;ll be my second time speaking at a conference. Earlier that year I also spoke at RailsConf.<\/p><p>My talk is about different <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2019\/\">best practices<\/a> - specifically when adhering to them breaks down. One of those best practices is high <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2019\/#code-coverage\">test coverage<\/a>. I start to work on the content for my presentation by building the code samples that I want to use in the slides. For the code coverage section, I&rsquo;m writing some code with some tests. I&rsquo;m using <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\">SimpleCov<\/a> to generate code coverage results.<\/p><p>As I&rsquo;m doing this, I find a perfect opportunity for a distraction: how does SimpleCov work? I do some quick investigation. The second sentence of the <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\/blob\/216b2d530bee7c4b8a8fe2898684924bfccfa79a\/README.md\">README<\/a> states:<\/p><blockquote><p>It uses Ruby&rsquo;s built-in Coverage library to gather code coverage data&hellip;<\/p><\/blockquote><p>That gave me a new distraction - what is this Coverage library that&rsquo;s built in to Ruby? I started with the documentation, that at the time looked something like <a href=\"https:\/\/ruby-doc.org\/stdlib-2.6.5\/libdoc\/coverage\/rdoc\/Coverage.html\">this<\/a>. Wanting to learn more, I pulled up Ruby&rsquo;s source code, and pretended I knew how to read C well. In reviewing the <code>coverage.c<\/code> file, which looked something like <a href=\"https:\/\/github.com\/ruby\/ruby\/blob\/ruby_2_6\/ext\/coverage\/coverage.c\">this<\/a>, I learned that it supports <a href=\"https:\/\/github.com\/ruby\/ruby\/blob\/ruby_2_6\/ext\/coverage\/coverage.c#L42-L48\">different modes<\/a> of coverage.<\/p><p>I spent an afternoon playing with the different modes. I built different examples that I could use in my presentation. None of it actually made the talk though - I didn&rsquo;t have enough time, and it wasn&rsquo;t the primary focus.<\/p><p>That doesn&rsquo;t mean I wasted all that effort though.<\/p><h3 id=\"rubyconf-2020\">RubyConf 2020 <a href=\"#rubyconf-2020\">\ud83d\udd17<\/a><\/h3><p>At least with the people I knew, there wasn&rsquo;t a lot of awareness of the Coverage module. People were very familiar with the concept of code coverage. No one I spoke with knew that Ruby has a built-in facility to measure it or how it worked.<\/p><p>Because of that, I submitted a <a href=\"https:\/\/kevinjmurphy.com\/posts\/enough-coverage-to-beat-the-band-proposal\/\">proposal<\/a> to talk about Coverage at RubyConf 2020. I already had some code samples documenting how to use it. I thought it would be a unique proposal. I had a ready-made theme to tie everything together. I had a lot of time in my house to work on it. If you&rsquo;re wondering why, consult some resource about what was going on in the world then.<\/p><p>The result is, as of this writing, my <a href=\"https:\/\/kevinjmurphy.com\/posts\/coverage\/\">favorite talk<\/a> I&rsquo;ve put together. I had so much fun with it, that I brought it back out in 2021, still at home, for a &ldquo;Ruby&rsquo;s Got You Covered&rdquo; <a href=\"https:\/\/kevinjmurphy.com\/speaking\/#enough-coverage-to-beat-the-band\">world tour<\/a>. I&rsquo;m very thankful to all those groups for accepting a virtual, not local, speaker at their event.<\/p><h3 id=\"documentation\">Documentation <a href=\"#documentation\">\ud83d\udd17<\/a><\/h3><p>At the beginning of this post, I was very specific in my wording to say this is my first &ldquo;code change&rdquo; accepted to Ruby. That&rsquo;s because it&rsquo;s not my first <strong>commit<\/strong> to Ruby.<\/p><p>At RubyConf 2020, <a href=\"https:\/\/github.com\/mame\">mame<\/a> was in the audience for my talk. Another benefit of a virtual conference: I couldn&rsquo;t see that, so I had no idea. As a result, I couldn&rsquo;t be nervous about it. They just so happen to have been the person that wrote most of the Coverage module&rsquo;s code.<\/p><p>We talked afterwards in the conference&rsquo;s chat platform, and that led to my <a href=\"https:\/\/github.com\/ruby\/ruby\/commit\/0026f644d739efed0d69911b434a1012ad55c393\">first commit<\/a> into Ruby. I added what I learned in preparing that talk into the documentation of the module. Now people don&rsquo;t have to look into the source code to discover the different modes.<\/p><p>In preparation for that 2021 world tour, I also created a <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/\">blog post<\/a> explaining coverage.<\/p><h2 id=\"covering-why-this-matters\">Covering Why This Matters <a href=\"#covering-why-this-matters\">\ud83d\udd17<\/a><\/h2><p>And <em>that<\/em> is the very long explanation about how I&rsquo;m interested in the Ruby 3.2.0 Coverage changes. And more specifically, how I noticed oneshot lines was missing as a supported mode.<\/p><p>Beyond my excitement about having a change (admittedly small) accepted into Ruby, I hope this inspires you to think about what you have to share. That may sound daunting. I know I never expected to commit anything to Ruby.<\/p><p>It started with me reading a lot of code and playing around in my terminal. Telling some friends turned into a talk. The talk led to an encounter with a Ruby committer that resulted in a documentation commit. Turning that into a blog post got me thinking about how much fun that talk was. That became a whole tour (where I stayed in my office the whole time). Now, I have a small modification to that area of the code in Ruby itself.<\/p><p>What&rsquo;s something neat you&rsquo;ve been playing with? What&rsquo;s the smallest step in your mind you could take to comfortably share that - even with one person? Years later, who knows what that might lead to.<\/p>"},{"title":"2022 Year-End Review","link":"https:\/\/kevinjmurphy.com\/posts\/2022-review\/","pubDate":"Sat, 17 Dec 2022 08:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/2022-review\/","description":"<p>This isn&rsquo;t a &ldquo;real&rdquo; post. This is a summary of all the things that made up my year in 2022. Thanks to all that were a part of it.<\/p><h2 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h2><p>I published 16 articles about Ruby or software development in general this year. I started with a series about what we as developers can apply from a <a href=\"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/\">MBA curriculum<\/a>. My last post of the year covered what I saw at <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/\">RubyConf Mini<\/a>.<\/p><p>In between, I wrote about <a href=\"https:\/\/kevinjmurphy.com\/posts\/find-or-initialize-by-block\/\">find_or_initialize_by<\/a>, RSpec <a href=\"https:\/\/kevinjmurphy.com\/posts\/request-spec-realistic-error-response\/\">request specs<\/a>, released my <a href=\"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/\">past conference proposals<\/a>, and more.<\/p><p>Mid-way through the year, I started a <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">newsletter<\/a> that I&rsquo;ve been sending out monthly. I&rsquo;m still not sure how I best want to structure that or what people want to read. If you subscribed, let me know what you like and what changes you might suggest. And if you&rsquo;re not subscribed? You can <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">change that<\/a> for next month!<\/p><p>My most-read posts are those that end up in other newsletters. This year, my writing appeared in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> 4 times, <a href=\"https:\/\/rubyradar.dev\/\">Ruby Radar<\/a> 3 times, and <a href=\"https:\/\/ruby.libhunt.com\/\">Awesome Ruby<\/a> 4 times. I appeared in the <a href=\"https:\/\/newsletter.shortruby.com\/\">Short Ruby Newsletter<\/a> and <a href=\"https:\/\/tinyletter.com\/developeravocados\/\">Developer Avacados Weekly<\/a> each once, which were both new for me this year.<\/p><p>This is down from last year, but I&rsquo;m not sure what to take away from that. It&rsquo;s possible that my writing this year wasn&rsquo;t as good as last year. My topics may not have been as good of a match for sharing in newsletters. It could be that there&rsquo;s a lot more content to choose from and more competition. It could be none of those things! What do you think?<\/p><h2 id=\"speaking\">Speaking <a href=\"#speaking\">\ud83d\udd17<\/a><\/h2><p>I ventured back out into the real, physical, world this year for conference speaking.<\/p><p>At RailsConf 2022, I opened up my <a href=\"https:\/\/kevinjmurphy.com\/posts\/browser-history\/\">browser history<\/a> to discuss how, and why, we search for things in our day-to-day job as developers.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/R7LkHjJdH9o\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><p>It&rsquo;s always an honor to present at Ruby Central conferences, and it was great to see so many people. But, I was pretty overwhelmed by the whole experience. Enough that I told myself I was going to take a while off from speaking.<\/p><p>And then RubyConf Mini came around in the fall. This seemed like a unique opportunity to support the community, and was very close to me in Providence, RI. I felt like I had to try to be a part of it, so I submitted. And I ended up presenting in the first slot of day 1, where we taught a computer how to <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">play guitar<\/a>.<\/p><p>Last year I shared how many proposals I submitted to how many conferences. I did not keep track of that this year. Without those numbers, I&rsquo;ll provide this general assessment. You can (and I did) submit more than one proposal to conferences. I had many more proposals rejected than accepted.<\/p><p>I was a guest on one podcast in 2022, but for two episodes - and one you never heard. I recorded an episode of <a href=\"https:\/\/www.buzzsprout.com\/1927628\">Code and the Coding Coders who Code it<\/a> with Drew Bragg that we lost at the hands of bad audio. But, we persevered and re-recorded a <a href=\"https:\/\/www.buzzsprout.com\/1927628\/10596923\">different episode<\/a>, which split into a <em>bonus<\/em> ep about <a href=\"https:\/\/www.buzzsprout.com\/1927628\/10597043-bonus-hockey-episode\">hockey<\/a>.<\/p><h2 id=\"reading\">Reading <a href=\"#reading\">\ud83d\udd17<\/a><\/h2><p>I was a technical reviewer for two books. Both for Noel Rappin and The Pragmatic Bookshelf again. I provided feedback on the second edition of <a href=\"https:\/\/pragprog.com\/titles\/tailwind2\/modern-css-with-tailwind-second-edition\/\">Modern CSS with Tailwind<\/a>, as I did for the first edition last year. That continues to be funny to me. It may be funny to you if you know my relative ability (or lack thereof) at accomplishing <em>anything<\/em> with CSS.<\/p><p>Noel is also bringing back the &ldquo;Pickaxe book&rdquo;. I reviewed part of <a href=\"https:\/\/pragprog.com\/titles\/ruby5\/programming-ruby-3-2-5th-edition\/\">Programming Ruby 3.2<\/a> this year as well.<\/p><h2 id=\"employer\">Employer <a href=\"#employer\">\ud83d\udd17<\/a><\/h2><p>I had a public <a href=\"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/\">job search<\/a> for this first time this year. I&rsquo;m very thankful to all the people who reached out, either with an opportunity, a helping hand, or both. I&rsquo;m happy to have ended up at <a href=\"https:\/\/www.bookbub.com\/\">BookBub<\/a>.<\/p><h2 id=\"new-to-me-in-2022\">New (to me) in 2022 <a href=\"#new-to-me-in-2022\">\ud83d\udd17<\/a><\/h2><p>I don&rsquo;t go out of my way to experience new things, instead retreating to known comforts. So, I don&rsquo;t have much in the way of brand new things to me this year. Here&rsquo;s some stuff that came out in 2022 that I, of course, was going to like.<\/p><h3 id=\"dance-fever\">Dance Fever <a href=\"#dance-fever\">\ud83d\udd17<\/a><\/h3><p>Florence Welch&rsquo;s voice is unmatched and the new Florence + The Machine album is a gem. &ldquo;Free&rdquo; is easily the best song I&rsquo;ve heard all year, and maybe for the last few years.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/1Zh1uDf3Glo\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"impera\">Impera <a href=\"#impera\">\ud83d\udd17<\/a><\/h3><p>Ghost is a delight. It&rsquo;s exactly the type of band that, if you described them to me, I would say I&rsquo;m not interested. But their music is too good, and too fun, so I don&rsquo;t care. Their new album is no exception.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/XHy1POIGkKM\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"normal-gossip\">Normal Gossip <a href=\"#normal-gossip\">\ud83d\udd17<\/a><\/h3><p>I already knew Kelsey McKinney from their writing on <a href=\"https:\/\/defector.com\/author\/kelsey-mckinney\/\">Defector<\/a> and their <a href=\"https:\/\/www.bookbub.com\/books\/god-spare-the-girls-by-kelsey-mckinney\">book<\/a>. This year I got into Kelsey McKinney the podcast host of <a href=\"https:\/\/audioboom.com\/channels\/5062384\">Normal Gossip<\/a>, the show that<\/p><blockquote><p>delivers juicy, strange, funny, and utterly banal gossip about people you\u2019ll never know and never meet.<\/p><\/blockquote><h2 id=\"thank-you\">Thank You <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>Thanks for reading, or listening. Thanks for being a friend. Thanks for doing your best to make the world around you as good as it can be. I hope there&rsquo;s more of that next year.<\/p>"},{"title":"RubyConf Mini 2022 Recap","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/","pubDate":"Sun, 20 Nov 2022 20:00:24 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-mini-2022-recap\/","description":"<h2 id=\"rubyconf-mini-2022\">RubyConf Mini 2022 <a href=\"#rubyconf-mini-2022\">\ud83d\udd17<\/a><\/h2><p>RubyConf Mini wrapped up last week in Providence, Rhode Island. This post documents the list of talks I saw. It&rsquo;s not intended as a review of any of the talks, but to highlight the variety of great work from all involved. I hope you&rsquo;ll seek out the full videos of all the sessions that interest you, once they are available.<\/p><h2 id=\"day-1\">Day 1 <a href=\"#day-1\">\ud83d\udd17<\/a><\/h2><p>The day started with an opening address, which I&rsquo;m sure was lovely, but I&rsquo;ll admit I was a bit distracted. The reason being that I got on stage directly after. Thanks to everyone who attended <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">my session<\/a>; it was great to see you all.<\/p><p>After that, I moved rooms to see Jeremy Smith&rsquo;s talk, <a href=\"https:\/\/www.rubyconfmini.com\/program#Solo:-Building-Successful-Web-Apps-By-Your-Lonesome\">&ldquo;Solo: Building Successful Web Apps By Your Lonesome&rdquo;<\/a>. The perspective of this talk was from a place of working alone. But, no matter your team size, the conclusions in this session are valuable and relevant.<\/p><p>Next I attended Stephanie Minn&rsquo;s <a href=\"https:\/\/www.rubyconfmini.com\/program#Empathetic-Pair-Programming-with-Nonviolent-Communication\">&ldquo;Empathetic Pair Programming with NonviolentCommunication&rdquo;<\/a>. I appreciated how Stephanie showed how to re-word comments in more actionable and helpful ways.<\/p><p>Lindsay Kelly spoke next on <a href=\"https:\/\/www.rubyconfmini.com\/program#Knowing-When-To-Walk-Away\">&ldquo;Knowing When To Walk Away&rdquo;<\/a>. This talk provided good questions to consider when evaluating your current job satisfaction. Questions to ask whether you&rsquo;re actively making a plan to leave a role or not.<\/p><p>We next heard from Barbara Tannenbaum in the first keynote on &ldquo;Persuasive Communication&rdquo;. The speakers I spoke with after were excitedly applying these tips in their sessions. Even if you&rsquo;re not public speaking, there&rsquo;s much to learn here.<\/p><p>The agenda for the day wrapped up with a Podcast Panel. All those voices you hear in your headphones are real people! Brittany Martin, Julie, Drew Bragg, Jo\u00ebl Quenneville and Andy Croll all took part in this conversation. I believe you should expect to hear it soon in your podcast feed, if you follow <em>any<\/em> of their shows.<\/p><h2 id=\"day-2\">Day 2 <a href=\"#day-2\">\ud83d\udd17<\/a><\/h2><p>I started the second day by throwing something out on social media that I want to highlight here. If you find yourself at a conference, and you see a speaker after a talk you were at, let them know that you were there. If there was something you liked about it (particularly if it&rsquo;s specific), tell them!<\/p><p>I can be a great conversation starter. It can be <strong>super<\/strong> meaningful to speakers. Being able to talk about my talk before and after the talk is my cheat code. It helps me feel more comfortable in social interactions in conferences. I&rsquo;m not great at going up and talking to people. If you saw my talk, or see my badge that says I&rsquo;m a speaker, maybe you&rsquo;ll bring it up. Now we have something in common to talk about.<\/p><p>Speakers put a lot of time and effort into their preparation. Knowing that people were there, and even better that they got something out of it, can make a big difference in <em>their<\/em> conference experience.<\/p><p>Nadia Odunayo started today&rsquo;s program with A Ruby Mystery Story keynote. It&rsquo;s a fun romp told in a delightful manner that&rsquo;s a very entertaining watch.<\/p><p>I stayed in the ballroom for Aji Slater to introduce <a href=\"https:\/\/www.rubyconfmini.com\/program#Zen-and-the-Art-of-Incremental-Automation\">&ldquo;Zen and the Art of Incremental Automation&rdquo;<\/a>. Beyond the theory of applying these changes, the fallout of practicing (or not) the tips provided a grounded perspective.<\/p><p>Vladimir Dementyev next took the stage for <a href=\"https:\/\/www.rubyconfmini.com\/program#Weaving-and-seaming-mocks\">&ldquo;Weaving and seaming mocks&rdquo;<\/a>. Vladimir is always pushing improved tooling and support in so many different areas. This talk was no different, introducing a different way of verifying mocks in tests.<\/p><p>Kevin Newton discussed <a href=\"https:\/\/www.rubyconfmini.com\/program#Syntax-Tree\">&ldquo;Syntax Tree&rdquo;<\/a> and how parsing code can lead to further inspection, debugging, and formatting opportunities.<\/p><p>Ernesto Tagwerker warned us that <a href=\"https:\/\/www.rubyconfmini.com\/program#Here-Be-Dragons:-The-Hidden-Gems-of-Tech-Debt\">&ldquo;Here Be Dragons: The Hidden Gems of Tech Debt&rdquo;<\/a>. It provided a fun classification of tools to surface problem areas in your code. After identifying potential issues, it proposed strategies to handle them.<\/p><p>At the Transgender Issues Panel, we listened to their stories. I thank them for sharing.<\/p><p>Julia Evans closed the program for the day with a keynote around DNS. This proposes strategies for long-term learning, providing tangible steps to follow.<\/p><h2 id=\"day-3\">Day 3 <a href=\"#day-3\">\ud83d\udd17<\/a><\/h2><p>Day 3 opened with a sponsor panel discussing how each of the sponsors provides an inclusive workspace, and what that means to them.<\/p><p>We then heard from a variety of speakers in Lightning Talks. We heard about what open source can learn from co-operatives, academic research about Ruby, and much more.<\/p><p>Andrea Fomera and Julie then spoke on <a href=\"https:\/\/www.rubyconfmini.com\/program#Inclusivity:-Hiring-is-just-the-beginning\">&ldquo;Inclusivity: Hiring is just the beginning&rdquo;<\/a>. I aspire to their honesty on stage and at work.<\/p><p>Brittany Martin then came in strong on short notice off the wait list to present<a href=\"https:\/\/www.rubyconfmini.com\/program#We-Need-Someone-Technical-on-the-Call\">&ldquo;We Need Someone Technical on the Call&rdquo;<\/a>. This provided a framework to prepare, execute, and follow up on a meeting.<\/p><p>Jo\u00ebl Quenneville then took the stage and started <a href=\"https:\/\/www.rubyconfmini.com\/program#Teaching-Ruby-to-Count\">&ldquo;Teaching Ruby to Count&rdquo;<\/a>. Introducing different iteration mechanisms is great. Jo\u00ebl taking the next step about how to apply that to our own code (and why) was a pleasure to watch.<\/p><p>Jenny Shih explained how to incorporate functional principles in our Ruby code during <a href=\"https:\/\/www.rubyconfmini.com\/program#Functional-programming-for-fun-and-profit!!\">Functional programming for fun and profit!!<\/a>. Jenny took great care to present these terms and philosophies in an approachable way that was immediately actionable.<\/p><p>Jared Norman next spoke of <a href=\"https:\/\/www.rubyconfmini.com\/program#TDD-on-the-Shoulders-of-Giants\">&ldquo;TDD on the Shoulders of Giants&rdquo;<\/a>. This explored different TDD styles, leaving us with patterns to apply in our tests.<\/p><p>Budding game show host Drew Bragg next asked, <a href=\"https:\/\/www.rubyconfmini.com\/program#Who-Wants-to-be-a-Ruby-Engineer?\">&ldquo;Who Wants to be a Ruby Engineer?&rdquo;<\/a>. Audience participants did their best on stage, battling with confusing and esoteric Ruby syntax. The energy in the room was buzzing.<\/p><p>Rose Wiegley closed the conference with a keynote on &ldquo;Leading From Where You Are&rdquo;. No matter your title at work, and whether you see yourself as a leader or not, I expect you&rsquo;ll learn a lot from this session.<\/p><h2 id=\"appreciation\">Appreciation <a href=\"#appreciation\">\ud83d\udd17<\/a><\/h2><p>Thanks to <a href=\"https:\/\/jemma.dev\/\">Jemma Issroff<\/a>, without whom this conference wouldn&rsquo;t exist. Without whom we wouldn&rsquo;t have had this time together. After RailsConf, I told myself I was taking a break from speaking for a while. And then this opportunity came along, and it was too compelling to not TRY to be a part of. Thanks for the push.<\/p><p>Thanks to <a href=\"https:\/\/www.emilysamp.dev\/\">Emily Samp<\/a> who helped all the speakers learn what they needed to know in preparation for the event.<\/p><p>Thanks to <a href=\"https:\/\/andycroll.com\/\">Andy Croll<\/a> for offering his prior experience running conferences.<\/p><p>I&rsquo;m not sure what to say to you, ballroom projector. You worked great during the A\/V check on Monday, but you weren&rsquo;t there for me in my time of need. You disappointed me. Thank you for playing nicer with the rest of the speakers.<\/p><p>Thank you to the <a href=\"https:\/\/www.confreaks.com\/\">Confreaks<\/a> and hotel A\/V staff, who professionally and calmly worked to troubleshoot the issue, while I blankly stared at the audience. When I&rsquo;m practicing, I tell myself I need to prepare enough to give my talk without any slides or notes. I practice on walks, away from my material. I practice looking the other direction (checking occasionally to make sure I&rsquo;m clicking to the right slide). In those moments on stage, I was wondering if my preparation would become necessary. Would I have to give my full talk without any slides?<\/p><p>Thanks <a href=\"https:\/\/www.drbragg.dev\/\">Drew Bragg<\/a> for unflinchingly handing me his laptop from the front row, letting me git clone the repo that has my slides on it. I ran the presentation from his computer, having never used it before.<\/p><p>Thanks to the conference WiFi. It actually WORKED that morning, allowing me to get my deck onto Drew&rsquo;s machine.<\/p><p>Thanks to <a href=\"https:\/\/twitter.com\/joelquen\">Jo\u00ebl Quenneville<\/a> for unsurprisingly being the first person to approach me after I finished on stage. He was quick with a reassuring compliment and gave me feedback that&rsquo;ll stick with me for quite some time.<\/p><p>Thanks to <a href=\"https:\/\/danielmagliola.com\/\">Daniel Magliola<\/a>, who couldn&rsquo;t be with us, for leaving a message in my inbox right around the time I wrapped up on stage. Just checking in on how it went. What a treat.<\/p><p>Thanks to my <a href=\"https:\/\/bookbub.com\/\">BookBub<\/a> colleagues who provided feedback on an early version of my talk, and covered for me at work so I could be here this week.<\/p><p>Thanks to my <a href=\"https:\/\/dockwa.com\/\">Dockwa<\/a> friends, some old and some new. It was great to spend time with you.<\/p><p>I&rsquo;m so happy <a href=\"https:\/\/konnor.netlify.app\/\">Konnor Rogers<\/a> joined us. Thanks for fitting us into your week&rsquo;s plans, and making time to come along.<\/p><p>Thanks to <a href=\"https:\/\/brittanymartin.dev\/\">Brittany Martin<\/a> for being as great a conversationalist in person as she is in an interview.<\/p><p>Thanks to anyone who scrolled through this whole thing, thinking, &ldquo;surely Kevin will mention me next.&rdquo; You&rsquo;re handling it great! (Also, sorry?)<\/p><p>Thanks to my wife, who tactfully asked, &ldquo;you&rsquo;re sure you&rsquo;re doing this again?&rdquo; only once when I told her I got accepted. It&rsquo;s not only the time this week to help with childcare, but all the time leading up to it she afforded me to prepare.<\/p><p>With that, I&rsquo;m exhausted, and overwhelmed, in a good way. Until next time, #RubyFriends &lt;3.<\/p>"},{"title":"Revisiting Calling Sonic Pi From Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/","pubDate":"Fri, 11 Nov 2022 07:00:24 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/","description":"<h2 id=\"anyone-can-play-guitar-series\">Anyone Can Play Guitar Series <a href=\"#anyone-can-play-guitar-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">Enumerating Musical Notes<\/a><\/li><li><strong>Revisiting Calling Sonic Pi From Ruby<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Programming Guitar Greatness<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">Composing Our Own Guitar Amps From Inherited Gear<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">Flog-Driven Development<\/a><\/li><\/ol><h2 id=\"tweaking-amp-settings\">Tweaking Amp Settings <a href=\"#tweaking-amp-settings\">\ud83d\udd17<\/a><\/h2><p><a href=\"https:\/\/sonic-pi.net\/\">Sonic Pi<\/a> is software to make sounds and music driven by code. Sonic Pi comes with an <a href=\"https:\/\/sonic-pi.net\/tutorial.html#section-1-2\">IDE<\/a> of sorts. You can program the composition you&rsquo;d like to play in the IDE. With one button, you get immediate feedback hearing how your code sounds.<\/p><p>A few years ago I wrote about controlling <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Sonic Pi with Ruby<\/a> code without needing to code in the IDE directly. That relied on the <a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\">sonic-pi-cli<\/a> gem. For my <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">Anyone Can Play Guitar (With Ruby)<\/a> talk, I took a different approach.<\/p><h2 id=\"updated-wiring\">Updated Wiring <a href=\"#updated-wiring\">\ud83d\udd17<\/a><\/h2><p>As of this writing, Sonic Pi has released version 4.3.0. It&rsquo;s progressed significantly since version 3.2, which is the version that I used in my original post. One of the consequences is that the sonic-pi-cli gem does not appear to work with later versions of Sonic Pi. I couldn&rsquo;t get earlier versions of Sonic Pi to work on my computer. I didn&rsquo;t want to deal with virtualization to see if I could get an earlier version of Sonic Pi working with the gem. I also didn&rsquo;t have the time to update the gem to work with later version of Sonic Pi. Sorry - I had a presentation to write!<\/p><p>I needed a quick way to achieve the same, or similar, results. So, I&rsquo;ll admit - I cheated.<\/p><h2 id=\"input-jack\">Input Jack <a href=\"#input-jack\">\ud83d\udd17<\/a><\/h2><p>Sonic Pi reads an <a href=\"https:\/\/github.com\/sonic-pi-net\/sonic-pi\/tree\/stable\/app\/config\/user-examples#initrb\">init file<\/a> every time that the application boots. You might use this file for helper methods to recall in the application&rsquo;s editor. Instead, I will store the code to play the song itself. It gets read when the application boots and starts playing the song.<\/p><p>It&rsquo;s not the greatest long-term solution. You don&rsquo;t want to hear the song every time you open the application forevermore. I know I don&rsquo;t. But it is good enough for demonstration purposes. That said, I still needed to construct the code to play the song.<\/p><h2 id=\"speaker\">Speaker <a href=\"#speaker\">\ud83d\udd17<\/a><\/h2><p>Much like in my <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">original version<\/a>, I built an amplifier to communicate with Sonic Pi. In my original post, my guitar class knew how to generate its <a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/#plucking-a-single-string\">sound output<\/a> to the amp. In this version, the amp knows how to do that.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SonicPiAmplifier<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">sound_output<\/span>(play_operation, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">25<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;with_synth :pluck do&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34; <\/span><span style=\"color:#e6db74\">#{<\/span>play_operation<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">, release: <\/span><span style=\"color:#e6db74\">#{<\/span>duration<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">)&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;end&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;sleep(<\/span><span style=\"color:#e6db74\">#{<\/span>duration<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">)<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">].<\/span>join(<span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>For each note to play, this is using <a href=\"https:\/\/sonic-pi.net\/tutorial.html#section-2-3\">Sonic Pi&rsquo;s DSL<\/a> to play a note with a <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/#noise-reduction\">synth patch<\/a> that sounds a bit like a guitar. It plays the note for the provided duration, and then sleeps for that same duration. That&rsquo;s so Sonic Pi won&rsquo;t immediately play the next note on top of this one.<\/p><h2 id=\"power-amp\">Power Amp <a href=\"#power-amp\">\ud83d\udd17<\/a><\/h2><p>To simplify, let&rsquo;s assume that amplifiers work by progressing sound through two components. Sound moves through a pre amp to a power amp. The power amp is what&rsquo;s responsible for sending the sound to the speaker. That&rsquo;s what will use the <code>sound_output<\/code> method we&rsquo;ve built.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SonicPiAmplifier<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">power_amp_stage<\/span>(sound)<\/span><\/span><span style=\"display:flex;\"><span> play_operation <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;play(:<\/span><span style=\"color:#e6db74\">#{<\/span>sound<span style=\"color:#f92672\">.<\/span>to_s<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> output <span style=\"color:#f92672\">=<\/span> sound_output(play_operation, <span style=\"color:#e6db74\">duration<\/span>: sound<span style=\"color:#f92672\">.<\/span>duration)<\/span><\/span><span style=\"display:flex;\"><span> @sounds <span style=\"color:#f92672\">&lt;&lt;<\/span> output<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> output<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The <code>play_operation<\/code> string is again a command from <a href=\"https:\/\/sonic-pi.net\/tutorial.html#section-2-1\">Sonic Pi&rsquo;s DSL<\/a>. As you&rsquo;d expect, it plays a sound. We retrieve the value of the sound to play from the <code>Note<\/code> class we constructed in a <a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">prior post<\/a>. We pass this into our <code>sound_output<\/code>. We store the result in our list of <code>@sounds<\/code> that the amplifier projects, and return it as well.<\/p><h2 id=\"audio-loopback\">Audio Loopback <a href=\"#audio-loopback\">\ud83d\udd17<\/a><\/h2><p>The reason we&rsquo;re keeping track of our <code>@sounds<\/code> is that we want to be able to replay them again after the fact. Our amplifier will use this ability to write the song it played to a file.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SonicPiAmplifier<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">write_to_file<\/span>(location)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>open(location, <span style=\"color:#e6db74\">&#34;w&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>file<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> @sounds<span style=\"color:#f92672\">.<\/span>each <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>sound<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> file<span style=\"color:#f92672\">.<\/span>write(sound)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ll write this to Sonic Pi&rsquo;s init file. Then, when we open up the application, it will play the song from the amplifier on boot.<\/p><h2 id=\"output\">Output <a href=\"#output\">\ud83d\udd17<\/a><\/h2><p>Time to take this for a rip. We&rsquo;ll grab a guitar, decide which song we want to play, plug in our amplifier we just built, and play the song.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> guitar <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Blues<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Guitar<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> guitar<span style=\"color:#f92672\">.<\/span>restring(<span style=\"color:#e6db74\">gauge_set<\/span>: <span style=\"color:#e6db74\">:srv<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> guitar<span style=\"color:#f92672\">.<\/span>tune(<span style=\"color:#e6db74\">:down_half_step<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> song <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Blues<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Shuffle<\/span><span style=\"color:#f92672\">.<\/span>new(guitar)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> amp <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Blues<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">SonicPiAmplifier<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">volume<\/span>: <span style=\"color:#ae81ff\">10<\/span>, <span style=\"color:#e6db74\">on<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> guitar<span style=\"color:#f92672\">.<\/span>plug_in(<span style=\"color:#e6db74\">amplifier<\/span>: amp)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> song<span style=\"color:#f92672\">.<\/span>play { <span style=\"color:#f92672\">|<\/span>measure<span style=\"color:#f92672\">|<\/span> measure<span style=\"color:#f92672\">.<\/span>map { <span style=\"color:#f92672\">|<\/span>sound<span style=\"color:#f92672\">|<\/span> puts sound } }<\/span><\/span><span style=\"display:flex;\"><span> amp<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Running this method in isolation won&rsquo;t push any air through our speakers. Instead, we need our cheat. We&rsquo;ll write all the sounds from the song to our init file, and then have our script open Sonic Pi in a <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Kernel.html#method-i-60\">subshell<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>init <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span><span style=\"color:#66d9ef\">Dir<\/span><span style=\"color:#f92672\">.<\/span>home<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">\/.sonic-pi\/config\/init.rb&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>play<span style=\"color:#f92672\">.<\/span>write_to_file(init)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">`open &#34;\/Applications\/Sonic\\ Pi.app&#34;`<\/span><\/span><\/span><\/code><\/pre><\/div><p>Sonic Pi will start, read the init file with all the instructions to play our song, and start playing.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/iQUNU36Vem4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"coda\">Coda <a href=\"#coda\">\ud83d\udd17<\/a><\/h2><p>Sam Aaron very helpfully on <a href=\"https:\/\/twitter.com\/samaaron\/status\/1592653823486889984\">Twitter<\/a> suggested a more reasonable approach that highlights built-in Sonic Pi functionality. With a combination of <a href=\"https:\/\/github.com\/sonic-pi-net\/sonic-pi\/blob\/dev\/etc\/doc\/tutorial\/A.02-live-coding.md\">live loops<\/a> and <a href=\"https:\/\/github.com\/sonic-pi-net\/sonic-pi\/blob\/dev\/etc\/doc\/tutorial\/12.1-Receiving-OSC.md\">OSC messages<\/a> I could have avoided relying on the init file. Thanks to Sam for the tip - and for Sonic Pi!<\/p><blockquote><p>One approach would be to implement some live_loop listeners to incoming OSC which you could then send to at your leisure from a separate pure Ruby process.<\/p><p>That way you can show off the best of both worlds!<\/p><\/blockquote><p>Let&rsquo;s take a step back and explore how to model the business domain of <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">playing guitar<\/a>.<\/p>"},{"title":"Anyone Can Play Guitar (With Ruby) - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/anyone-can-play-guitar-proposal\/","pubDate":"Thu, 10 Nov 2022 20:26:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/anyone-can-play-guitar-proposal\/","description":"<h1 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h1><p>I&rsquo;ve got the blues. I&rsquo;ve been looking for the perfect guitar tone, but haven&rsquo;t found it. To amp up my mood, let&rsquo;s teach a computer to play the guitar through an amplifier.<\/p><p>Let&rsquo;s string together object-oriented principles to orchestrate a blues shuffle. We&rsquo;ll model our domain with the help of inheritance, composition, and dependency injection. This talk will strike a chord with you, whether you&rsquo;ve strummed a guitar before or not.<\/p><h1 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h1><p>To build a guitar and amplifier, this talk will introduce software design concepts. As we unwrap different requirements and intricacies, we&rsquo;ll explore new concepts.<\/p><p>We&rsquo;ll begin by <strong>modeling<\/strong> some objects in our domain. This will focus on how we can identify objects in our system. [7 - 10 minutes]<\/p><p>We&rsquo;ll find different objects in our system the following ways:<\/p><ul><li>Some objects mirror the real-world counterpart that we&rsquo;re implementing. We can decompose the overall object into its component parts. We could have a <code>Guitar<\/code> class that does everything you need a guitar to do. In our exploration, we&rsquo;ll model our guitar as having a set of <code>String<\/code>s (not the data type) that work together.<\/li><li>Some objects become clear after identifying a need to pass around related data and behavior. Rather than using a hash with the individual notes played, we&rsquo;ll make a <code>Sound<\/code> class. That will be able to tell you if the notes combine to form a particular chord. This new behavior can live with the raw note data in this new class.<\/li><li>A bundle of private methods only related to part of the entire class can become a separate object. It takes some work for a <code>String<\/code> to determine which note it&rsquo;s playing. We need that information to construct our <code>Sound<\/code>. It&rsquo;s also a bit misplaced within the full context of the <code>String<\/code>&rsquo;s responsibilities. By introducing a <code>Fret<\/code> class, we can relieve the <code>String<\/code> class from that work. We can rigorously test that part of the code in isolation. We also create a dedicated location for related behavior to live.<\/li><\/ul><p>Our electric guitar is only one part of our system. We need to hear the sound with the help of an amplifier. When building our amplification needs, we&rsquo;ll explore <strong>inheritance<\/strong>. [7 - 10 minutes]<\/p><ul><li>We&rsquo;ll start with an <code>Amplifier<\/code> class that will make a noise when you call the <code>project<\/code> method to it, passing it a <code>Sound<\/code>. Of course, it only makes a noise if the amp is on. The <code>project<\/code> method is the last step to <code>amplify<\/code> a sound. <code>amplify<\/code> is the method that&rsquo;s called externally.<\/li><li>There are different types of amplifiers. The different components affect the way they sound. We&rsquo;ll build amplifiers that inherit from the core <code>Amplifier<\/code> functionality. They&rsquo;ll extend that base to exhibit their differences.<\/li><li>A <code>TubeAmplifier<\/code> uses electrical circuitry like a light bulb. They need to warm up to sound their best. They need to account for how warmed up they are as part of the <code>amplify<\/code> process.<\/li><li>A <code>SolidStateAmplifier<\/code> uses more modern transistors and semiconductors. Because of its components, applying too much distortion results in harsh audio clipping. You wouldn&rsquo;t hear that from a tube amp.<\/li><\/ul><p>Our amplifier design must incorporate <strong>composition<\/strong> to build the next type of amplifier. [5 minutes]<\/p><ul><li>A <code>HybridAmplifier<\/code> has a tube pre amp, and a solid-state power amp. It needs elements of both the <code>TubeAmplifier<\/code> <em>and<\/em> the <code>SolidStateAmplifier<\/code>. We can&rsquo;t inherit from both in Ruby. Instead, we&rsquo;ll compose <code>TubeAmplification<\/code> and <code>SolidStateAmplification<\/code> modules together to produce a single <code>HybridAmplifier<\/code>.<\/li><li>The value in composing with modules is in sharing behavior generally. It&rsquo;s not specific to addressing the lack of multiple inheritance in the language. We&rsquo;ll apply this concept back to guitars. An acoustic guitar has a sound hole that will <code>project<\/code> the sound. An electric guitar cannot do that without the help of an amplifier. An acoustic guitar is <strong>not<\/strong> an amplifier though. Instead, we&rsquo;ll have a <code>SoundProjection<\/code> module. Both an acoustic guitar and an amplifier can use this module. The amplifier will use it as the basis of its amplification of the sound. The acoustic guitar will use it only to project its sounds.<\/li><\/ul><p>We&rsquo;ll combine our guitar and amplifier with the use of <strong>dependency injection<\/strong>. [3 minutes]<\/p><ul><li>Initially, our guitar will create an instance of an <code>Amplifier<\/code> on object construction. It&rsquo;ll use that amplifier so we can hear the sounds we&rsquo;re playing.<\/li><li>Our <code>Amplifier<\/code> will <code>put<\/code> the note that it plays to the screen. We don&rsquo;t want everyone to hear that noise when we practice. We&rsquo;ll build a separate <code>PracticeAmplifier<\/code> that outputs nothing. Now when we practice (in our tests), we don&rsquo;t hear (or see) the result of our practice.<\/li><li>To use our <code>PracticeAmplifier<\/code>, we need to add mocks in our tests. When our guitar makes a new <code>Amplifier<\/code>, it&rsquo;ll be a <code>PracticeAmplifier<\/code>.<\/li><li>We can make this easier by instead passing an instance of the amplifier we want to use to the guitar instance. Now our tests can use the practice amp without mocking.<\/li><li>This <em>also<\/em> improves the usage of our guitar generally. Now we can plug our guitar into the different amplifiers we&rsquo;ve created. We&rsquo;re not stuck with the base model. Though it started as a testing benefit, this change helps our application as well.<\/li><\/ul><p>At the end, we&rsquo;ll use the code we&rsquo;ve written in our presentation, and some we haven&rsquo;t, for a performance. We&rsquo;ll show how we can play a blues riff, either through <a href=\"https:\/\/sonic-pi.net\/\">Sonic Pi<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Sheet_music\">sheet music<\/a>, or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Tablature#Guitar_tablature\">guitar tablature<\/a>.<\/p><h3 id=\"notes-on-time-estimates\">Notes on time estimates <a href=\"#notes-on-time-estimates\">\ud83d\udd17<\/a><\/h3><p>The <strong>modeling<\/strong> and <strong>inheritance<\/strong> section estimates are longer to introduce the business domain. <strong>Composition<\/strong> will use terminology that&rsquo;s already introduced. We&rsquo;ll reuse and change code we&rsquo;ve already written. <strong>Dependency injection<\/strong> ties together the two subsystems we&rsquo;re already familiar with.<\/p><h1 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h1><p>This talk will serve a wide audience, though in different ways. For early career developers, this can introduce these concepts. It&rsquo;ll apply them in a (hopefully) fun way, and should demystify some of the fancy terminology.<\/p><p>For intermediate developers, the domain modeling section will have the most value. Identifying and extracting objects out of an existing system may be new to them. They may be familiar with seeing tutorials or writing their own work from scratch. They likely have had fewer opportunities to work with existing code in need of a change.<\/p><p>Even if you know everything about object-oriented design, this talk may be for you. Experienced developers will leave with another ready-made analogy for these concepts. They can use this example to explain the topics to others with a more novel problem space that the classics. There will be no discussion of animal and species taxonomies here.<\/p><p>I&rsquo;ve had the pleasure to speak at RubyConf and RailsConf in the past. I&rsquo;ve used (different) musical equipment to speak about (different) technical concepts effectively before.<\/p><h1 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h1><p>Kevin Murphy lives near Boston, Massachusetts, where he is a Software Developer at BookBub. Kevin <strong>owns<\/strong> two guitars, but feels it&rsquo;s an insult to the instrument to say he knows how to <strong>play<\/strong> the guitar.<\/p>"},{"title":"Available on Mastodon","link":"https:\/\/kevinjmurphy.com\/posts\/joining-mastodon\/","pubDate":"Sat, 05 Nov 2022 18:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/joining-mastodon\/","description":"<p>I&rsquo;ve created an account on Mastodon, specifically on the ruby.socialinstance. You can follow along and connect with me <a href=\"https:\/\/ruby.social\/@kevin_j_m\">here<\/a>.<\/p>"},{"title":"Enumerating Musical Notes","link":"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/","pubDate":"Thu, 13 Oct 2022 17:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/","description":"<h2 id=\"anyone-can-play-guitar-series\">Anyone Can Play Guitar Series <a href=\"#anyone-can-play-guitar-series\">\ud83d\udd17<\/a><\/h2><ol><li><strong>Enumerating Musical Notes<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Revisiting Calling Sonic Pi From Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Programming Guitar Greatness<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">Composing Our Own Guitar Amps From Inherited Gear<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">Flog-Driven Development<\/a><\/li><\/ol><p>I&rsquo;m teaching a computer how to play the guitar for <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">RubyConf Mini 2022<\/a>. Let&rsquo;s understand some basics before focusing on the guitar. Guitars can play many musical notes - actually, all of them! In this post, we&rsquo;ll output which note we play with the help of Ruby&rsquo;s <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html\">Array<\/a> class and <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Enumerable.html\">Enumerable<\/a> module.<\/p><h2 id=\"note-notation\">Note Notation <a href=\"#note-notation\">\ud83d\udd17<\/a><\/h2><p>There are only so many notes in music. 12 in fact. This is not going to be a lesson in music theory, so I&rsquo;ll spare you the details. We&rsquo;ll store each of the notes in an array.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>notes <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:a_flat<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:a<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:b_flat<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:b<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:c<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:d_flat<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:d<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:e_flat<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:e<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:f<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:g_flat<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:g<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"chromatic-scale\">Chromatic Scale <a href=\"#chromatic-scale\">\ud83d\udd17<\/a><\/h2><p>Putting all these notes together forms the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Chromatic_scale\">Chromatic scale<\/a>. Most often, you&rsquo;ll see the scale start with the &ldquo;C&rdquo; note.<\/p><p>Our collection of notes doesn&rsquo;t start with &ldquo;C&rdquo; though.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>notes<span style=\"color:#f92672\">.<\/span>first<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:a_flat<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can search for where &ldquo;C&rdquo; is in our array using the <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-index\">index<\/a> method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>notes<span style=\"color:#f92672\">.<\/span>index(<span style=\"color:#e6db74\">:c<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">4<\/span><\/span><\/span><\/code><\/pre><\/div><p>The scale starts at the fifth note in our collection. Ruby arrays are zero-indexed; the first element is at position, or index, zero. Index four, where our &ldquo;C&rdquo; is, is the fifth element. We&rsquo;d like it to be the <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-first\">first<\/a>, and have all the other notes follow in the expected order. We can use <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-rotate\">rotate<\/a> with our index to accomplish this.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>chromatic <span style=\"color:#f92672\">=<\/span> notes<span style=\"color:#f92672\">.<\/span>rotate(<span style=\"color:#ae81ff\">4<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span>, <span style=\"color:#e6db74\">:d_flat<\/span>, <span style=\"color:#e6db74\">:d<\/span>, <span style=\"color:#e6db74\">:e_flat<\/span>, <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">:f<\/span>, <span style=\"color:#e6db74\">:g_flat<\/span>, <span style=\"color:#e6db74\">:g<\/span>, <span style=\"color:#e6db74\">:a_flat<\/span>, <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">:b_flat<\/span>, <span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>To make it a little more clear where that 4 came from, we can use the return from our call to <code>index<\/code> as the argument to <code>rotate<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>chromatic <span style=\"color:#f92672\">=<\/span> notes<span style=\"color:#f92672\">.<\/span>rotate(notes<span style=\"color:#f92672\">.<\/span>index(<span style=\"color:#e6db74\">:c<\/span>))<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:c<\/span>, <span style=\"color:#e6db74\">:d_flat<\/span>, <span style=\"color:#e6db74\">:d<\/span>, <span style=\"color:#e6db74\">:e_flat<\/span>, <span style=\"color:#e6db74\">:e<\/span>, <span style=\"color:#e6db74\">:f<\/span>, <span style=\"color:#e6db74\">:g_flat<\/span>, <span style=\"color:#e6db74\">:g<\/span>, <span style=\"color:#e6db74\">:a_flat<\/span>, <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">:b_flat<\/span>, <span style=\"color:#e6db74\">:b<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"octaves\">Octaves <a href=\"#octaves\">\ud83d\udd17<\/a><\/h2><p>Aren&rsquo;t there are more than 12 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Musical_note\">sounds<\/a> that comprise the entirety of possible music? Yes! These notes repeat themselves at a higher frequency. The same note with double the frequency is an octave.<\/p><p>Our collection of notes has a definitive <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-last\">end<\/a> though.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>chromatic<span style=\"color:#f92672\">.<\/span>last<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:b<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;d like to repeat the collection to play the next octave. We&rsquo;ll treat our notes as a series that we can <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Enumerable.html#method-i-cycle\">cycle<\/a> through again and again.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>note_cycle <span style=\"color:#f92672\">=<\/span> chromatic<span style=\"color:#f92672\">.<\/span>cycle<\/span><\/span><\/code><\/pre><\/div><p>Calling <code>cycle<\/code> with no block <em>and<\/em> not providing a value for the number of times to cycle returns an <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Enumerator.html\">Enumerator<\/a> that cycles forever.<\/p><p>To demonstrate, let&rsquo;s ask for 40 elements from our cycle.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>result <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#ae81ff\">40<\/span><span style=\"color:#f92672\">.<\/span>times { result <span style=\"color:#f92672\">&lt;&lt;<\/span> note_cycle<span style=\"color:#f92672\">.<\/span>next }<\/span><\/span><span style=\"display:flex;\"><span>result<span style=\"color:#f92672\">.<\/span>count(<span style=\"color:#e6db74\">:c<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">3<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our <code>notes<\/code> array only has 12 elements. By calling <code>cycle<\/code> on it this way, we infinitely repeat this progression. &ldquo;C&rdquo; shows up many times in our result, as we progress from one octave to the next.<\/p><h2 id=\"classical-music\">Classical Music <a href=\"#classical-music\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s build a Note class to encapsulate this behavior. We&rsquo;ll accept a starting point that we&rsquo;ll call the starting note. We&rsquo;ll find the starting note in our notes collection, and start our infinite cycle with that note.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(<span style=\"color:#e6db74\">starting_note<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @note_cycle <span style=\"color:#f92672\">=<\/span> notes<span style=\"color:#f92672\">.<\/span>rotate(notes<span style=\"color:#f92672\">.<\/span>index(starting_note))<span style=\"color:#f92672\">.<\/span>cycle<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our Note class will also accept an offset - a number representing how far from our starting note we&rsquo;ll stray. This is how far into our cycle we should reach to determine which note value we&rsquo;re representing.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(<span style=\"color:#e6db74\">starting_note<\/span>:, <span style=\"color:#e6db74\">offset<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @note_cycle <span style=\"color:#f92672\">=<\/span> notes<span style=\"color:#f92672\">.<\/span>rotate(notes<span style=\"color:#f92672\">.<\/span>index(starting_note))<span style=\"color:#f92672\">.<\/span>cycle<\/span><\/span><span style=\"display:flex;\"><span> @offset <span style=\"color:#f92672\">=<\/span> offset<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We need to find our final note. We&rsquo;ll <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-take\">take<\/a> the collection of the cycle we traverse to move from the starting note to our offset. We&rsquo;ll return the <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html#method-i-last\">last<\/a> element of that progression as the value of our note.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">note_progression<\/span><\/span><\/span><span style=\"display:flex;\"><span> @note_cycle<span style=\"color:#f92672\">.<\/span>take(@offset <span style=\"color:#f92672\">+<\/span> <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">value<\/span><\/span><\/span><span style=\"display:flex;\"><span> note_progression<span style=\"color:#f92672\">.<\/span>last<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>While we don&rsquo;t need the full progression now, it&rsquo;ll come in handy soon.<\/p><h2 id=\"stringing-together-notes\">Stringing Together Notes <a href=\"#stringing-together-notes\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s bring this back to our goal - playing a note on a guitar. We tune each string on a guitar to a particular note. Plucking the string and letting it ring out will play the note it&rsquo;s tuned to - its starting note.<\/p><p>We&rsquo;ll get that value by setting the starting note of our Note class to be the note the guitar string is tuned to with an offset of 0.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>value<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:a<\/span><\/span><\/span><\/code><\/pre><\/div><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/a_note.png\" alt=\"Letting the A string ring out on a guitar plays an A note.\"><\/figure><p>You can play more notes on a guitar string than that. A guitar&rsquo;s neck has many sections, called frets. Pressing down on the string on a fret and plucking the string produces a higher frequency sound. That&rsquo;s the next note in our cycle.<\/p><p>Each fret will be a different offset from the same starting note. We can determine which note we play on a string tuned to &ldquo;A&rdquo;, pressing down on the third fret, with our Note class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">3<\/span>)<span style=\"color:#f92672\">.<\/span>value<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:c<\/span><\/span><\/span><\/code><\/pre><\/div><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/c_note.png\" alt=\"Playing the A string on a guitar with your finger on the third fret plays a C note.\"><\/figure><h2 id=\"fretting-about-octaves\">Fretting About Octaves <a href=\"#fretting-about-octaves\">\ud83d\udd17<\/a><\/h2><p>The number of frets on a guitar varies. A typical guitar may have between 18-24. That means that a string will be able to play the same note over again.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>value<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:a<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">12<\/span>)<span style=\"color:#f92672\">.<\/span>value<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:a<\/span><\/span><\/span><\/code><\/pre><\/div><p>These are both &ldquo;A&rdquo; notes, but at different octaves. We want our Note class to keep track of the octave so these don&rsquo;t seem to be playing exactly the same frequency.<\/p><p>To do this, we first need to know the octave number of our starting note. We&rsquo;ll change our constructor again.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(<span style=\"color:#e6db74\">starting_note<\/span>:, <span style=\"color:#e6db74\">starting_octave<\/span>:, <span style=\"color:#e6db74\">offset<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @note_cycle <span style=\"color:#f92672\">=<\/span> notes<span style=\"color:#f92672\">.<\/span>rotate(notes<span style=\"color:#f92672\">.<\/span>index(starting_note))<span style=\"color:#f92672\">.<\/span>cycle<\/span><\/span><span style=\"display:flex;\"><span> @starting_octave <span style=\"color:#f92672\">=<\/span> starting_octave<\/span><\/span><span style=\"display:flex;\"><span> @offset <span style=\"color:#f92672\">=<\/span> offset<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We progress to the next octave when we repeat the Chromatic scale - starting at a &ldquo;C&rdquo; note. This is where the full progression of notes from the starting note to our resulting value will help. We&rsquo;ll <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Enumerable.html#method-i-count\">count<\/a> the number of &ldquo;C&rdquo; notes we encounter in our progression. That&rsquo;s how many octaves we traversed.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">octave<\/span><\/span><\/span><span style=\"display:flex;\"><span> @starting_octave <span style=\"color:#f92672\">+<\/span> note_progression<span style=\"color:#f92672\">.<\/span>count(<span style=\"color:#e6db74\">:c<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Playing the starting note will have the same octave as is passed in. Playing the same note 12 frets up is the next octave.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">12<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"can-you-c-an-error\">Can you &ldquo;C&rdquo; an error? <a href=\"#can-you-c-an-error\">\ud83d\udd17<\/a><\/h2><p>Our implementation is a bit naive. If we have a string tuned to &ldquo;C&rdquo;, and we play the starting note, we expect it to return the same octave passed in. However, it does not.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:c<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;re counting the number of &ldquo;C&rdquo; notes we encounter in our progression. When we start with &ldquo;C&rdquo;, we&rsquo;re guaranteed to get one, but we haven&rsquo;t changed octaves.<\/p><p>We need to handle this special case. We&rsquo;ll check if our starting note is &ldquo;C&rdquo; and exclude the starting note from our octave count if that&rsquo;s the case.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Note<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">c_start?<\/span><\/span><\/span><span style=\"display:flex;\"><span> @note_cycle<span style=\"color:#f92672\">.<\/span>first <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">:c<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">octaves_progressed<\/span><\/span><\/span><span style=\"display:flex;\"><span> cs_passed <span style=\"color:#f92672\">=<\/span> note_progression<span style=\"color:#f92672\">.<\/span>count(<span style=\"color:#e6db74\">:c<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> c_start?<\/span><\/span><span style=\"display:flex;\"><span> cs_passed <span style=\"color:#f92672\">-=<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> cs_passed<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">octave<\/span><\/span><\/span><span style=\"display:flex;\"><span> @starting_octave <span style=\"color:#f92672\">+<\/span> octaves_progressed<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now we account for this edge case, while still working for other starting notes.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:c<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:c<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">12<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">0<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Note<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">starting_note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">starting_octave<\/span>: <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#e6db74\">offset<\/span>: <span style=\"color:#ae81ff\">12<\/span>)<span style=\"color:#f92672\">.<\/span>octave<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"closing-notes\">Closing Notes <a href=\"#closing-notes\">\ud83d\udd17<\/a><\/h2><p>We now have a class that can tell us which note we play on any string of our guitar. We leveraged Ruby&rsquo;s standard library to handle most of our logic. The <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Array.html\">Array<\/a> class and <a href=\"https:\/\/ruby-doc.org\/core-3.1.2\/Enumerable.html\">Enumerable<\/a> module worked in concert with our domain knowledge. We didn&rsquo;t need to write any algorithms or complex transformations ourselves. And now our computer knows how to play musical notes.<\/p><p>That sounds great to me.<\/p><p>Let&rsquo;s keep exploring using Ruby to make music by touching on an integration with <a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Sonic Pi<\/a>.<\/p>"},{"title":"Anyone Can Play Guitar (With Ruby)","link":"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/","pubDate":"Thu, 22 Sep 2022 17:00:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve got the blues. I&rsquo;ve been looking for the perfect guitar tone, but haven&rsquo;t found it. To amp up my mood, let&rsquo;s teach a computer to play the guitar through an amplifier.<\/p><p>Let&rsquo;s string together object-oriented principles to orchestrate a blues shuffle. We&rsquo;ll model our domain with the help of inheritance, composition, and dependency injection. This talk will strike a chord with you, whether you&rsquo;ve strummed a guitar before or not.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/t0C75Z0LX20\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/youtu.be\/t0C75Z0LX20\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/anyone-can-play-guitar\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/ruby-play-guitar#code-examples\">Code Examples<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/anyone-can-play-guitar-proposal\/\">Proposal<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/enumerating-musical-notes\/\">Enumerating Musical Notes<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/programming-guitar-greatness\/\">Programming Guitar Greatness<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/composing-our-own-guitar-amps-from-inherited-gear\/\">Composing Our Own Guitar Amps From Inherited Gear<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">Revisiting Calling Sonic Pi From Ruby<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/flog-driven-development\/\">Flog-Driven Development<\/a><\/li><\/ul><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/iQUNU36Vem4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe>"},{"title":"Symmetric Spies: RSpec Test Organization","link":"https:\/\/kevinjmurphy.com\/posts\/symmetric-spies\/","pubDate":"Sat, 17 Sep 2022 08:53:21 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/symmetric-spies\/","description":"<h2 id=\"special-operation-briefing\">Special Operation Briefing <a href=\"#special-operation-briefing\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re managing a network of secret agents. We need to send sensitive information to our <a href=\"https:\/\/en.wikipedia.org\/wiki\/Mole_(espionage)\">mole<\/a> who has infiltrated their target.<\/p><p>To do so, our agents will only send a scrambled version of the message. The mole will have to convert that into meaningful text.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Agent<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pass_secret<\/span>(message, <span style=\"color:#e6db74\">to<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> to<span style=\"color:#f92672\">.<\/span>accept_secret(<span style=\"color:#66d9ef\">Cipher<\/span><span style=\"color:#f92672\">.<\/span>encode(message))<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our mole can receive this random-seeming string of characters. After deciphering the real meaning, they hold on to it for later.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Mole<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">accept_secret<\/span>(message)<\/span><\/span><span style=\"display:flex;\"><span> messages <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Cipher<\/span><span style=\"color:#f92672\">.<\/span>decode(message)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We want to make sure our message-passing scheme works. But we don&rsquo;t want to go through the laborious process of encoding and decoding these messages. It&rsquo;s computationally intensive and not the focus of our test. The <code>Cipher<\/code> is a collaborator in our class, but it&rsquo;s not the star of the show. We can test that implementation, and bear those costs, in the unit tests of the <code>Cipher<\/code> class. We&rsquo;re testing the <code>Agent<\/code> class today, not <code>Cipher<\/code>.<\/p><h2 id=\"efficient-espionage-expectations\">Efficient Espionage Expectations <a href=\"#efficient-espionage-expectations\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;ll avoid encoding the message, but make sure it happens, by asking RSpec to <a href=\"https:\/\/relishapp.com\/rspec\/rspec-mocks\/docs\/basics\/expecting-messages\">expect the message<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;encodes the message&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> agent <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Agent<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> mole <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Mole<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:encode<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> agent<span style=\"color:#f92672\">.<\/span>pass_secret(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>, <span style=\"color:#e6db74\">to<\/span>: mole)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our test passes. We&rsquo;ve verified <code>pass_secret<\/code> encodes the message provided to it. We can move on; however, I&rsquo;d prefer a different test construction.<\/p><h2 id=\"organization-infiltration\">Organization Infiltration <a href=\"#organization-infiltration\">\ud83d\udd17<\/a><\/h2><p>The domain we&rsquo;re testing is about obfuscation, but I want as few surprises as possible in my tests. I like to follow a familiar pattern of how to construct my tests, so they read from top to bottom in a consistent way.<\/p><p>Some people refer to this as <a href=\"http:\/\/xunitpatterns.com\/Four%20Phase%20Test.html\">four-phase tests<\/a> (setup, exercise, verify, teardown). Others call it the <a href=\"https:\/\/wiki.c2.com\/?ArrangeActAssert\">AAA pattern<\/a> (arrange, act, assert).<\/p><p>Name aside, we want a convention that we can rely on. That makes it easier to scan a test and understand where we are in the process as a reader. The difficulty with our existing test is it breaks that convention. We state what we want to verify, or assert, before exercising, or acting on, the unit under test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;encodes the message&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># setup<\/span><\/span><\/span><span style=\"display:flex;\"><span> agent <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Agent<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> mole <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Mole<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># verification<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:encode<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># exercise<\/span><\/span><\/span><span style=\"display:flex;\"><span> agent<span style=\"color:#f92672\">.<\/span>pass_secret(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>, <span style=\"color:#e6db74\">to<\/span>: mole)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"double-agent\">Double Agent <a href=\"#double-agent\">\ud83d\udd17<\/a><\/h2><p>We can change our test to follow this pattern by using <a href=\"https:\/\/relishapp.com\/rspec\/rspec-mocks\/docs\/basics\/spies\">spies<\/a>. Our <code>Cipher<\/code> will be a <a href=\"https:\/\/relishapp.com\/rspec\/rspec-mocks\/docs\/basics\/partial-test-doubles\">partial double<\/a>, and we&rsquo;ll assert we call the <a href=\"https:\/\/relishapp.com\/rspec\/rspec-mocks\/docs\/basics\/spies#spy-on-a-method-on-a-partial-double\">method we&rsquo;re spying on<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;encodes the message&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># arrange<\/span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:encode<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> agent <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Agent<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> mole <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Mole<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># act<\/span><\/span><\/span><span style=\"display:flex;\"><span> agent<span style=\"color:#f92672\">.<\/span>pass_secret(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>, <span style=\"color:#e6db74\">to<\/span>: mole)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># assert<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to have_received(<span style=\"color:#e6db74\">:encode<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>In our <code>allow<\/code> statement, we have no constraints on what we pass to <code>encode<\/code>. That&rsquo;s fine in this case - we tighten up that restriction in the assertion. There we make sure not only that we call the <code>encode<\/code> method on <code>Cipher<\/code>, but also with the right input (&ldquo;hello&rdquo;).<\/p><p>I call this symmetry, or symmetric spies. We start our test by <code>allow<\/code>ing the message to be received. We end our test by resolving to <code>expect<\/code> that the message has been received.<\/p><h2 id=\"phase-2-of-the-operation\">Phase 2 of the Operation <a href=\"#phase-2-of-the-operation\">\ud83d\udd17<\/a><\/h2><p>As of now, we&rsquo;ve verified that our agent encodes the message. We also want to make sure the mole receives the message. The <strong>encoded<\/strong> message. And we want to verify that without actually running our message through the encoder.<\/p><p>Luckily, RSpec can help us out here too. We can use <code>with<\/code> and <code>and_return<\/code> on our test double. With those two methods, the double will provide the specified output, given that input.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>allow(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:encode<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#e6db74\">&#34;mystery&#34;<\/span>)<\/span><\/span><\/code><\/pre><\/div><p><code>Ciper.encode<\/code> will only return <code>&quot;mystery&quot;<\/code> if we call it with <code>&quot;hello&quot;<\/code> as an argument.<\/p><p>Let&rsquo;s use this to write a test that makes sure the mole gets our encoded message.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;sends the encoded message to the receiver&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> agent <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Agent<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> mole <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Mole<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> allow(<span style=\"color:#66d9ef\">Cipher<\/span>)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:encode<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>and_return(<span style=\"color:#e6db74\">&#34;mystery&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> allow(mole)<span style=\"color:#f92672\">.<\/span>to receive(<span style=\"color:#e6db74\">:accept_secret<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> agent<span style=\"color:#f92672\">.<\/span>pass_secret(<span style=\"color:#e6db74\">&#34;hello&#34;<\/span>, <span style=\"color:#e6db74\">to<\/span>: mole)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(mole)<span style=\"color:#f92672\">.<\/span>to have_received(<span style=\"color:#e6db74\">:accept_secret<\/span>)<span style=\"color:#f92672\">.<\/span>with(<span style=\"color:#e6db74\">&#34;mystery&#34;<\/span>)<\/span><\/span><\/code><\/pre><\/div><h2 id=\"breaking-the-code-or-symmetry\">Breaking the Code, or Symmetry <a href=\"#breaking-the-code-or-symmetry\">\ud83d\udd17<\/a><\/h2><p>You may have noticed something in that last test that feels like it betrays the intent of symmetry. We are spying (with partial doubles) on two methods. We only expect to receive one. That&rsquo;s not symmetrical at all!<\/p><p>We already tested that <code>pass_secret<\/code> will call <code>encode<\/code>. We have existing coverage verifying that. The focus of <strong>this test<\/strong> is ensuring the encoded message gets to the mole. We&rsquo;re using our partial double on <code>Cipher<\/code> to avoid a method call that takes a long time. Another use case may be to avoid a side-effect that we don&rsquo;t want to incur in our test. We&rsquo;re not using it to verify system behavior.<\/p><p>We <em>are<\/em> setting up the mole as a partial double to verify behavior. We want to make sure the agent interacts with the mole as expected. Because of that, we verify that we call the right method, with the right input, to the mole. We don&rsquo;t verify how we encode the message.<\/p><p>There would be nothing <em>wrong<\/em> with asserting that we call <code>encode<\/code> on <code>Cipher<\/code>. It&rsquo;s at best duplicative, given our prior test. Yet, it does take away focus on the intent of our test, which is verifying communication with the mole.<\/p><p>The goals of the test drive what expectations we make in that test. The goal of this test isn&rsquo;t about encoding; it&rsquo;s about communicating with the mole. Seeking symmetry isn&rsquo;t having the same number of doubles or spies and expectations. It&rsquo;s organizing the test in a manner consistent with the four-phase test or AAA pattern.<\/p><h2 id=\"debriefing\">Debriefing <a href=\"#debriefing\">\ud83d\udd17<\/a><\/h2><p>We may not <em>need<\/em> the symmetry within an individual test. Keeping it, and our test organization scheme, in mind helps us write readable, predictable tests. The element of surprise may be critical to a spy, agent, or mole in the field. It&rsquo;s less of an asset in your test suite.<\/p>"},{"title":"Sharing Past Conference Proposals","link":"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/","pubDate":"Thu, 04 Aug 2022 21:00:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/sharing-past-conference-proposals\/","description":"<h2 id=\"conference-talk-preparation-series\">Conference Talk Preparation Series <a href=\"#conference-talk-preparation-series\">\ud83d\udd17<\/a><\/h2><ol><li><strong>Sharing Past Conference Proposals<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/building-conference-talk-content\/\">Building Conference Talk Content<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/preparing-conference-talk-delivery\/\">Preparing Conference Talk Delivery<\/a><\/li><\/ol><h2 id=\"happy-cfp-season\">Happy CFP Season! <a href=\"#happy-cfp-season\">\ud83d\udd17<\/a><\/h2><p>Is it time to respond to a CFP? CFP stands for Call For Proposals. Conferences are looking for speakers, and they want to hear from you. I hope you&rsquo;ll consider submitting.<\/p><p>To celebrate, I&rsquo;ve published each of my <a href=\"https:\/\/kevinjmurphy.com\/tags\/conference-proposal\/\">accepted proposals<\/a>. This is definitely not me procrastinating on writing new proposals. This post is a whimsical summary of things that worked for me that can help with your proposal.<\/p><p>Why am I only including accepted proposals? Well, first of all, because that&rsquo;s all I want to do. Secondly, I may decide to resubmit an updated version of a past proposal. I don&rsquo;t want to publicly identify myself.<\/p><p>Some review processes are anonymous. I&rsquo;m sure every current and future Program Committee member reads all my posts (hi, everyone!). I wouldn&rsquo;t want everyone to have to recuse themselves because they already know who wrote it. How would it ever get accepted then?<\/p><p>Let&rsquo;s dig in to some thoughts you might consider in your proposal, using mine as an example.<\/p><h2 id=\"build-your-outline\">Build your outline <a href=\"#build-your-outline\">\ud83d\udd17<\/a><\/h2><p>There are differing opinions on how long your proposal should be. As you can tell from reading this, I tend to be verbose. I try to tamp it down, but, I&rsquo;ve got to <a href=\"#be-honest-about-yourself\">be me<\/a>. Whether it&rsquo;s bullet points, sentences, or paragraphs, you need to define the scope of your topic.<\/p><p>What are the main points you want to cover? What will the audience take away from attending your session? The Program Committee needs to know in your proposal. The details section is where I provide this information. There&rsquo;s no need to keep any secrets or hold anything back. If there is something you want to have as a bit of a surprise to the audience, you can keep it out of the abstract. But if you know what your plan is, I recommend having it in your details section. I creatively titled this the &ldquo;Outline&rdquo; in my <a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-alternatives-proposal\/\">evaluating alternatives<\/a> proposal.<\/p><p>This also helps you once the Program Committee accepts your proposal. Congratulations! Now you need to turn the proposal into a talk. Where do you start? Your outline gives you a starting point. You&rsquo;re not beholden to it. It might flow in a different order. You might cut sections, or combine them, or add new ones. But you&rsquo;re not starting from scratch. You already helped yourself out in the past with the outline.<\/p><h2 id=\"revisit-conventions-and-norms\">Revisit conventions and norms <a href=\"#revisit-conventions-and-norms\">\ud83d\udd17<\/a><\/h2><p>You don&rsquo;t need to invent a technology, principle, or methodology to have a conference talk. Sharing your experience with a well-known concept is a great contribution. I talked about the potential downsides of some of our favorite conventions in <a href=\"https:\/\/kevinjmurphy.com\/posts\/dont-hang-me-out-to-dry-proposal\/\">Don&rsquo;t Hang Me Out To DRY<\/a>.<\/p><p>That doesn&rsquo;t mean that your perspective needs to be controversial or a &ldquo;hot take&rdquo;. The number of first-time conference attendees is large! This may be the opportunity for early-career developers to learn these time-honored traditions we love, or love to hate.<\/p><h2 id=\"write-what-you-know-or-what-you-want-to-know\">Write what you know, or what you want to know <a href=\"#write-what-you-know-or-what-you-want-to-know\">\ud83d\udd17<\/a><\/h2><p>Where do proposal ideas come from? For me, they&rsquo;re not born out of a CFP being open. I have a private repository that I keep updated throughout the year with ideas for talks. Some of them may be a sentence. Some of them may be a title. Some may be a link to something I did, or read, and want to flesh out more.<\/p><p>Most of the time, my proposals are about things that I know. That&rsquo;s my comfort zone. That&rsquo;s what I did for <a href=\"https:\/\/kevinjmurphy.com\/posts\/fake-it-while-you-make-it-proposal\/\">Fake It While You Make It<\/a>. I was on a project that dealt with a lot of third-party dependencies. I explored different options for testing our interactions with them. I felt comfortable sharing that with an audience. I felt that my experience could help someone else.<\/p><p>Proposals can also be about things that you <em>want<\/em> to know more about. If you have a lot of passion and interest in developing your skills in a certain area, it can be a forcing function. You don&rsquo;t need to be an expert to submit the proposal. You need to do enough work to be comfortable and confident in what you&rsquo;re proposing. You need to project that to the Program Committee. Forward-looking proposals can accelerate your learning. They can also be very enticing to the Program Committee looking for new ideas.<\/p><h2 id=\"go-all-in-with-a-theme\">Go all-in with a theme <a href=\"#go-all-in-with-a-theme\">\ud83d\udd17<\/a><\/h2><p>All my talks have some thematic grounding or some world that they live it. The talks from my proposals above were about food research for SPAM, onboarding for a new role at a &ldquo;perfect&rdquo; company, and building the latest in sweater weather detection technology. All those scenarios came to be <em>after<\/em> proposal acceptance. They aren&rsquo;t present in the proposal.<\/p><p>It doesn&rsquo;t have to be that way though. If you have a theme already picked out that you know the talk will focus on - lean into it. Make it clear in the proposal. Draw attention to it in the abstract. Spell out how you&rsquo;ll tie all your examples back to your theme in the details. I did that in the proposal for my <a href=\"https:\/\/kevinjmurphy.com\/posts\/enough-coverage-to-beat-the-band-proposal\/\">coverage talk<\/a>. It was always going to be about supporting a band on tour. There was no need to hide it - I couldn&rsquo;t do the talk any other way in my head.<\/p><p>As an aside, this is easily my favorite talk I&rsquo;ve written to date. The theme definitely helped with that. I got to escape into that world for a bit, and somewhat avoid the real world in the Fall of 2020. (It wasn&rsquo;t the greatest, for a lot of reasons. If you&rsquo;re wondering why, you must be reading this from the far future. Consult your nearest history book.).<\/p><p>This talk was actually born out of my &ldquo;Don&rsquo;t Hang Me Out To DRY&rdquo; talk. I was procrastinating writing the actual talk. Instead I investigated how all these code coverage tools work. I needed to cut all that out from the talk because I didn&rsquo;t have enough time. But, because I keep a list of possible talk topics, I revisited it next year.<\/p><p>Given that it&rsquo;s a talk about live music, one day I want to to deliver this talk to a live, in-person audience.<\/p><h2 id=\"incorporate-learnings-from-outside-the-community\">Incorporate learnings from outside the community <a href=\"#incorporate-learnings-from-outside-the-community\">\ud83d\udd17<\/a><\/h2><p>Conference talks about &ldquo;what <code>x<\/code> taught me about programming&rdquo; may feel like an already-covered trope. I think the reason it comes up so much is because it <em>works<\/em>. We&rsquo;re all grounded in the community we share as developers. But our individual experiences are wider than that. Those different perspectives can have value to others in the community. I&rsquo;ve been so bold as to suggest that a developer can learn a thing or two from a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-proposal\/\">MBA program<\/a>.<\/p><p>What have you done outside of development that we can incorporate into our practices? Whether it be theatre <a href=\"https:\/\/www.youtube.com\/watch?v=JWDPRWPtOeg\">set design<\/a>, <a href=\"https:\/\/www.youtube.com\/watch?v=mrdmHK6ogC0\">famous bards<\/a>, <a href=\"https:\/\/www.youtube.com\/watch?v=7zW_BBiTLAY\">woodworking<\/a>, or anything else, it can make for a compelling proposal topic.<\/p><h2 id=\"be-honest-about-yourself\">Be honest about yourself <a href=\"#be-honest-about-yourself\">\ud83d\udd17<\/a><\/h2><p>Proposals, and conference talks, should be a reflection of you. They should demonstrate what&rsquo;s interesting and exciting to you, in your voice. Make sure you have the conviction to follow through on anything in your proposal. Someone may suggest some feedback that you don&rsquo;t think you can deliver, or agree with. Even if it guarantees acceptance, I would be hard-pressed to make the change. While I ultimately find it rewarding, writing a talk <em>after<\/em> getting accepted is (for me) a lot of work. It&rsquo;d be even harder to do if I wasn&rsquo;t bought into it.<\/p><p>My <a href=\"https:\/\/kevinjmurphy.com\/posts\/browser-history-confessional-proposal\/\">browser history confessional proposal<\/a> started off as a joke or a dare to myself. &ldquo;Wouldn&rsquo;t it be funny to get up on stage and tell the world all the things that I actually searched for to help me with my job?&rdquo; I thought to myself. Then the Program Committee called my bluff, and accepted it. Thankfully, I bought into the idea. In developing the proposal, I uncovered a greater theme than the initial joke. If I wasn&rsquo;t willing to see it through to the stage, I couldn&rsquo;t have hit the Submit button.<\/p><h2 id=\"your-turn\">Your Turn <a href=\"#your-turn\">\ud83d\udd17<\/a><\/h2><p>When people ask me for resources about writing a proposal, I currently send them:<\/p><ul><li><a href=\"https:\/\/noelrappin.com\/blog\/2014\/01\/conference-prompts-or-how-to-submit-proposals-and-influence-people\/\">Conference Prompts: Or How to Submit Proposals and Influence People<\/a><\/li><li><a href=\"https:\/\/noelrappin.com\/blog\/2014\/03\/what-i-learned-from-reading-429-conference-proposals\/\">What I learned from reading 429 conference proposals<\/a><\/li><li><a href=\"http:\/\/www.sarahmei.com\/blog\/2014\/04\/07\/what-your-conference-proposal-is-missing\/\">What Your Conference Proposal Is Missing<\/a><\/li><li><a href=\"https:\/\/schneems.com\/blogs\/2016-04-07-conference-proposal\">Get Your Conference Proposal Accepted<\/a><\/li><li><a href=\"https:\/\/www.deconstructconf.com\/blog\/how-to-prepare-a-talk\">How to prepare a talk<\/a><\/li><li><a href=\"https:\/\/thoughtbot.com\/blog\/conference-talk-proposal-examples\">Conference Talk Proposal Examples<\/a><\/li><li><a href=\"https:\/\/www.youtube.com\/watch?v=l9JXH7JPjR4\">How to Talk to Developers (video)<\/a><\/li><li>Review <a href=\"https:\/\/speakerline.io\/proposals\">other proposals<\/a> (including <a href=\"https:\/\/speakerline.io\/speakers\/96\">mine<\/a>)<\/li><\/ul><p>What can you share with everyone? What are you passionate about? What do you want to learn more about? Take all that energy and turn it into a proposal! Send me a link showing your name in the conference program so we can celebrate together.<\/p>"},{"title":"Browser History Confessional: Searching My Recent Searches - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/browser-history-confessional-proposal\/","pubDate":"Wed, 03 Aug 2022 21:28:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/browser-history-confessional-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>We all only have so much working memory available in our brains. Developers may joke about spending their day composing search engine queries. The reason it&rsquo;s a joke is because of the truth behind it. Search-driven development is a reality.<\/p><p>Join me, and my actual search history, on a journey to solve recent challenges I faced. I&rsquo;ll categorize the different types of information I often search for. You&rsquo;ll leave with tips on retrieving the knowledge you need for your next bug, feature, or pull request.<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>The situations where I reach for external knowledge are generally one of the following:<\/p><h3 id=\"syntax-and-implementation-details\">Syntax and Implementation Details <a href=\"#syntax-and-implementation-details\">\ud83d\udd17<\/a><\/h3><p>Do you remember how to format a date as a string in a specific convention? I sure don&rsquo;t! But if I need to do so, I revisit the documentation for <a href=\"https:\/\/apidock.com\/ruby\/DateTime\/strftime\">strftime<\/a>. It includes a great list of directives to reference.<\/p><p>Even if I&rsquo;m pretty sure I remember the correct directives, I still take a moment to review this list. There may be a shorthand or convention that I can use to achieve the format I need.<\/p><p>ActiveRecord&rsquo;s API surface is large, and includes many methods that look to do the same thing. Even though they may give you the same data, they may have different side-effects. One of these side-effects is in performance. Reviewing posts like Nate Berkopec&rsquo;s &ldquo;<a href=\"https:\/\/www.speedshop.co\/2019\/01\/10\/three-activerecord-mistakes.html\">3 ActiveRecord Mistakes That Slow Down Rails Apps: Count, Where and Present<\/a>&rdquo; can remind me, in the moment, of the trade-offs. Re-reading it forces me to consider what my situation demands, and what will be most performant.<\/p><p>I don&rsquo;t have the capacity (or interest) in committing this information to memory. But I do know exactly where I need to go to find it. It&rsquo;s not an introductory search each time - I know exactly what I&rsquo;m looking for <strong>and<\/strong> where to get the answers I need. I can&rsquo;t hold the state of the world in my head, but I can keep breadcrumbs or bookmarks to know how to retrieve the data I need.<\/p><h3 id=\"solving-direct-problems\">Solving Direct Problems <a href=\"#solving-direct-problems\">\ud83d\udd17<\/a><\/h3><p>I recently got a new computer for work. It&rsquo;s the first time I&rsquo;ve had to set up a computer in four years. I installed everything I thought I needed, cloned a repository, and bundled the application. It didn&rsquo;t succeed. In the backtrace, I saw:<\/p><pre tabindex=\"0\"><code>fatal error: &#39;libpq-fe.h&#39; file not found<\/code><\/pre><p>I did not have the first clue how to fix that. But I did trust that I wasn&rsquo;t the only person to ever see that. So, I did the most productive next step I could think of. I copied the error, exactly as written, and pasted it into a search engine. It turns out that the pg gem has a much easier time installing on a computer that has postgres installed. One search result clicked on, one command issued to my package manager, and I was on my way to the next problem, never to consider &ldquo;libpq-fe.h&rdquo; again.<\/p><p>The story of these errors isn&rsquo;t always so neatly resolved. However, I&rsquo;m hard-pressed to consider a next step with a higher success rate. Searching the internet for the <em>exact<\/em> error message can be a life saver! We&rsquo;ll talk about where to source this information from, such as:<\/p><ul><li>The project&rsquo;s issue tracker.<\/li><li>The project&rsquo;s published mailing list.<\/li><li>A discussion forum.<\/li><li>A trusty generic search query.<\/li><\/ul><p>Now with some answers, we need to determine which solutions might actually work. We&rsquo;ll keep track of different things we&rsquo;ve tried so we don&rsquo;t find ourselves going in circles. We&rsquo;ll practice stepping away to take a break. We&rsquo;ll even consider reaching out to an actual human being for another perspective.<\/p><h3 id=\"revisiting-assumptions-and-approaches\">Revisiting Assumptions and Approaches <a href=\"#revisiting-assumptions-and-approaches\">\ud83d\udd17<\/a><\/h3><p>For help with identifying N+1 queries, I reach for the <a href=\"https:\/\/github.com\/flyerhzm\/bullet\">bullet<\/a> gem. Like any tool, it&rsquo;s not perfect, has its particular quirks, and has a learning curve. But, I&rsquo;m comfortable with it and mostly know how to fight with it, if I need to. That familiarity carries a lot of value, but must not be the only consideration when reaching for a tool. By taking a few minutes to see the current state of the world for evaluating N+1 queries, I may discover the new(er) <a href=\"https:\/\/github.com\/palkan\/n_plus_one_control\">n_plus_one_control<\/a> gem.<\/p><p>There are many tools, many ways, and many approaches to solving problems. Reaching for what you know can have immediate efficiencies and comfort. Our world as developers is rapidly-changing. It&rsquo;s important to keep an eye out for any new (to you) philosophies or approaches. This doesn&rsquo;t mean chasing &ldquo;the new hotness&rdquo;. It does suggest awareness of what&rsquo;s possible, what exists, and what warrants further evaluation. Now may not be the time to introduce that new tool or that new design pattern. That five minute investigation may pay off months later when you think, &ldquo;what was that thing I was looking at a while ago? That will apply perfectly right now.&rdquo;<\/p><h3 id=\"sharing-communal-knowledge\">Sharing Communal Knowledge <a href=\"#sharing-communal-knowledge\">\ud83d\udd17<\/a><\/h3><p>Calling <code>update_all<\/code> won&rsquo;t trigger ActiveRecord <a href=\"https:\/\/apidock.com\/rails\/ActiveRecord\/Base\/update_all\/class\">callbacks<\/a>, and that includes the <code>updated_at<\/code> timestamp. RSpec includes a <code>have_enqueued_job<\/code> <a href=\"https:\/\/relishapp.com\/rspec\/rspec-rails\/docs\/matchers\/have-enqueued-job-matcher\">matcher<\/a> that can verify you enqueued ActiveJob jobs. One of Sandi Metz&rsquo;s <a href=\"https:\/\/thoughtbot.com\/blog\/sandi-metz-rules-for-developers\">rules<\/a> is to have methods that are no longer than five lines. Not to brag, but these are things that I happen to know.<\/p><p>Why am I searching for them then? I want to share that information with others, and provide original sources for them. This often arises in code review. I could recommend using <code>have_enqueued_job<\/code> in a spec. If the author hasn&rsquo;t heard of that before, I&rsquo;m now putting <em>more<\/em> work on them. I should bolster that by adding a link to the documentation in my comment, along with a code snippet. I&rsquo;m giving them a path forward, and a reference to learn more the implementation details.<\/p><p>This may become one of their items that they never remember the details of. Much like me and <code>strftime<\/code>. But now they know where to go each time to get that information. I&rsquo;ve benefited from this kindness of reviewers in my past.<\/p><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>Stumbling through <strong>real<\/strong> searches I&rsquo;ve made, people may feel better about their own ability to solve problems. We can&rsquo;t always do it alone; we need to seek external resources to help some times. At the least, we can commiserate on how we find ourselves looking up the same things every time. We may all have a different &ldquo;thing&rdquo; we continually look up, but I&rsquo;m convinced we all have at least one.<\/p><p>Beyond the awkward goal of embarrassing myself so others can relate, there is the real truth that developers need to search for things. A lot. The search methodology I use, and the reasons I&rsquo;m searching at all, should help others in their next search.<\/p><h2 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h2><p>Kevin lives near Boston, Massachusetts, where he is a Software Developer.<\/p><p>Consulting a search engine for Kevin Murphy will show luxury hair care products, an actor, and a University of Chicago Economist. None of that information has much to do with me, unfortunately.<\/p>"},{"title":"Engineering MBA - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-proposal\/","pubDate":"Wed, 03 Aug 2022 21:27:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>Improve your work as a developer with an introduction to strategic planning,situational leadership, and process management. No balance sheets or incomestatements here; join me to learn the MBA skills valuable to developers withoutthe opportunity costs of lost wages or additional student loans.<\/p><p>Demystify the strategic frameworks your management team may use to makedecisions and learn how you can use those same concepts in your daily work.Explore the synergy one developer achieved by going to business school (sorry,the synergy comment slipped out - old habit).<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>The purpose of this talk is to introduce key concepts from an MBA program thathave proven valuable to me as a software developer. These aren&rsquo;t focused ontools to help you start your own business, or run your own department as amanager. There will be no math, no financial models, and no marketing plans.These topics are items that I learned during my MBA curriculum that I put touse in my day-to-day work as an individual contributor developer.<\/p><p>For each of these, I&rsquo;ll introduce the topic and then explain how it&rsquo;s applicableto developers.<\/p><h3 id=\"situational-leadership\">Situational Leadership <a href=\"#situational-leadership\">\ud83d\udd17<\/a><\/h3><h4 id=\"introduction\">Introduction <a href=\"#introduction\">\ud83d\udd17<\/a><\/h4><p>Different tasks call for different leadership styles, and an effective team orleader can adapt their style as needed. Based on both the assignment and thepeople involved, the support, guidance, and process required to complete thework should vary. The Situational Leadership model, first introduced in\u201c<a href=\"https:\/\/www.amazon.com\/Management-Organizational-Behavior-10th-Hersey\/dp\/0132556405\/ref=dp_ob_title_bk\">Management of Organizational Behavior<\/a>\u201d,suggests four different leadership styles.<\/p><h5 id=\"directing\">Directing <a href=\"#directing\">\ud83d\udd17<\/a><\/h5><p>Directing is considered the formative stage of leadership, where the team orindividuals may not have much context to navigate independently in a successfulmanner, like if one is new to a team. Even if someone has been on a team for along time, the directing style can be beneficial if the work is new (orunfamiliar), but can be accomplished by following a recipe. When using thisstyle in creating a ticket to be added to the sprint, ensure it contains aclearly defined set of instructions for what to do. If work is assigned toparticular individuals during sprint planning, ensure there\u2019s a second memberassigned to this work, someone who has done something similar before, so thatthey can work together and learn from each other to accomplish the goal.<\/p><h5 id=\"coaching\">Coaching <a href=\"#coaching\">\ud83d\udd17<\/a><\/h5><p>Rather than following a recipe, as in the directing style, when coaching,instructions may still be required to lead to a successful outcome, but theyare better structured as suggestions or a jumping off point. This gives theperson assigned the task the freedom, and encouragement, to put their own spinon the work. Find past examples of solutions to similar problems in the past,and link to them in the ticket. The purpose is not to enforce or take thestance that these things <strong>are<\/strong> the same and should be the same. Instead,point out that investigation should be conducted by the team working on it todetermine how similar these tickets are and what applicability the othersolution has to this current task.<\/p><h5 id=\"supporting\">Supporting <a href=\"#supporting\">\ud83d\udd17<\/a><\/h5><p>When team members have relevant experience to complete the work successfully,but may be missing the self-confidence to deliver the work, a supportingleadership style may aid the task while also benefiting the growth of the teammember. It\u2019s unlikely, but still encouraged if felt necessary by the teammember, to have an upfront conversation about implementation details on how tocomplete the task. Instead, daily standup or other natural touchpoints are abetter opportunity to help finish the task. The team member may be betterhelped by checking in on how they\u2019re feeling about the work and providing themample time to talk with you about their progress or any issues they\u2019ve comeacross.<\/p><h5 id=\"delegating\">Delegating <a href=\"#delegating\">\ud83d\udd17<\/a><\/h5><p>The delegation leadership style is best applied for a team member who is bothhighly motivated and very skilled in the particular task. The need forsupervision is minimal, and the primary delegation task for the leader is toget out of the way and clear any impediments the team member may have. That is,of course, a component in all of these styles, but it is essentially all that\u2019sleft in this stage.<\/p><p>Most importantly, this is an opportunity to pull other team members forward onthis continuum with this task. Should it be a safe opportunity to do so, wherethere aren\u2019t intense time pressures or other risk factors, this is absolutelywhen the experienced person assigned to this work should be working alongside aless familiar colleague to share their knowledge and expertise, for the benefitof all.<\/p><h4 id=\"developer-applicability\">Developer Applicability <a href=\"#developer-applicability\">\ud83d\udd17<\/a><\/h4><p>Thinking through these styles can be helpful when engaged in sprint planningmeetings or backlog refinement\/grooming sessions. Situational Leadership can beused to determine how to parcel out, organize, or describe the work to be done.It\u2019s important to note that it\u2019s not sufficient to use one particular style,even towards a specific team member, for all units of work. A team member maybe able to be left alone and faithfully execute one task, where the delegationstyle will work best. That same team member may be completely unfamiliar withanother aspect of the project, where the directing style will be most helpful.<\/p><p>In order to round out the familiarity amongst the team with areas of theapplication or technical concepts, certain work may be recommended forparticular team members, with the knowledge and understanding that theappropriate support is required. However, if there is a time-sensitive orextremely important unit of work that must be addressed, it may be moreappropriate to play to the strengths (and familiarity) of a team member whorecently worked in that space to complete the ticket.<\/p><p>Situational Leadership dynamics can also play out in pairing sessions. Ifyou\u2019re able to ascertain your pair\u2019s comfort with a particular task, you canuse that information to shape the pairing session to meet everyone\u2019s needs. Forexample, someone completely new to a topic may not have the context to navigatearound the area being worked on, so while the Situational Leadership modelwould suggest a \u201cDirecting\u201d style, in a pairing session, that may manifestitself more like a \u201cTour Guide\u201d where the pair with more context provides thebackground and story for the work being completed while driving themselves, andallowing ample time for questions and clarification as the task is movedthrough - along with plenty of opportunity for interactivity, allowing theother pair to build up their familiarity with the work being done.<\/p><h3 id=\"competitive-advantage\">Competitive Advantage <a href=\"#competitive-advantage\">\ud83d\udd17<\/a><\/h3><h4 id=\"introduction-1\">Introduction <a href=\"#introduction-1\">\ud83d\udd17<\/a><\/h4><p>Your competitive advantage, a concept largely attributed to the work of<a href=\"https:\/\/www.amazon.com\/Competitive-Advantage-Creating-Sustaining-Performance\/dp\/0684841460\/ref=sr_1_1?crid=35XM4IZRB6LRZ&amp;keywords=michael+porter+competitive+advantage&amp;qid=1579645935&amp;sprefix=michale+porter+comp%2Caps%2C147&amp;sr=8-1\">Michael Porter<\/a>,is what sets you apart from others. This section will discuss the &ldquo;generic&rdquo;strategies to achieving a competitive advantage.<\/p><p>A core competency is part of what drives your competitive advantage, and yourefforts should be focused on maximizing effort spent there while minimizingeffort elsewhere. Core competency is a term that was popularized thanks to thework of <a href=\"https:\/\/scholar.google.com\/scholar_url?url=https:\/\/sites.google.com\/site\/ifim0911\/The_Core_Competence_of_the_Corporati.pdf&amp;hl=en&amp;sa=X&amp;scisig=AAGBfm2idIlXpXLeQmHlybZybf8HY1erAg&amp;nossl=1&amp;oi=scholarr\">Prahalad and Hamel<\/a>.<\/p><h5 id=\"cost\">Cost <a href=\"#cost\">\ud83d\udd17<\/a><\/h5><p>The most obvious strategy, but least sustainable over the long-term, is to bethe lowest-cost provider. This may be feasible if you can reliably produce yourproduct or offering in a way that competitors cannot, or if you have a massiveVC runway and raised a sizable round of investment. This is also the easieststrategy to replicate. Firms employing this strategy must be careful not to bedriven to a race to the bottom, at which point there is no cost advantage andanother strategy is the only way to succeed long-term. If we were building ablogging platform, then to have cost be a competitive advantage, you would needto sell your product cheaper than any other competitor.<\/p><h5 id=\"differentiation\">Differentiation <a href=\"#differentiation\">\ud83d\udd17<\/a><\/h5><p>If one is intentionally operating in a way that is unique in the landscape,they are looking to capitalize on the competitive advantage of differentiation.This highlights certain compelling or important characteristics for potentialcustomers and accentuates your ability to deliver on those items. To furtherour blogging platform example, this may be a concierge service thatautomatically (at least in the eyes of the customer) brings in any writing thecustomer has already done and imports it onto the new platform. The blogcompany may be able to charge a premium for that service, and attract morecustomers, if it meets the needs of customers in ways that other platforms donot.<\/p><h5 id=\"focus\">Focus <a href=\"#focus\">\ud83d\udd17<\/a><\/h5><p>The focus strategy tends to manifest itself by identifying a particular segmentof the market and tailoring an offering that\u2019s specific to those customers. Forexample, the blogging platform may focus on doctors by building a feature thatprovides a commentmoderation system which scans text for possible HIPAA violations and holdscomments in a temporary queue until they\u2019ve been approved. If you\u2019re not adoctor or a healthcare worker, that feature may not be valued very highly inyour decision ofblogging platform (though hopefully it\u2019s something you can appreciate); however,if you are a doctor, that can be a deciding factor in your choice of whichplatform to start your blog on.<\/p><h4 id=\"developer-applicability-1\">Developer Applicability <a href=\"#developer-applicability-1\">\ud83d\udd17<\/a><\/h4><p>If you\u2019re building functionality to send a user an email and your applicationhappens to be an email delivery service, then it\u2019s (hopefully) in the bestinterests of the company to be writing and using your own software to manageall the infrastructure and processes involved with sending an email. However,if that is not your company\u2019s business, then you\u2019re likely much better offrelying on a company that focuses on email delivery and focusing <em>your<\/em> customdevelopment on the areas that are truly unique about your organization.<\/p><h3 id=\"process-management\">Process Management <a href=\"#process-management\">\ud83d\udd17<\/a><\/h3><h4 id=\"introduction-2\">Introduction <a href=\"#introduction-2\">\ud83d\udd17<\/a><\/h4><p>Identifying and proposing informed changes that help achieve our goals for ourproducts, our companies, and our daily lives is critical to facilitatecontinuous improvement. Business Process Reengineering is the strategy ofdocumenting the current state of different processes and critically evaluatingthe value of each step to propose a more streamlined future process. <a href=\"https:\/\/hbr.org\/1990\/07\/reengineering-work-dont-automate-obliterate\">Michael Hammer<\/a>is credited with developing this work.<\/p><h4 id=\"developer-applicability-2\">Developer Applicability <a href=\"#developer-applicability-2\">\ud83d\udd17<\/a><\/h4><p>Developers are continually introducing and refining processes for their endusers; we call them features. Managing, benchmarking, and refining theseprocesses can be critical to the success or failure of an overall product.<\/p><p>More personally, we all have our particular workflows we follow. By evaluatingour tools or the steps we take to deliver our work, we may become moreefficient and eliminate what Business Process Reengineering practitioners maycall \u201cwasteful\u201d or \u201clow-value\u201d activities. For example, when interacting withgit, this may mean writing new aliases for commonly-used commands, learningdifferent shortcuts, using a different git client entirely, or learning toeliminate the need for entire commands, like using git pull rather than gitfetch and git merge.<\/p><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>After two years spent earning an MBA full-time, I returned to the workforcejust as I left it: a software developer. The knowledge I gained over those twoyears is much more applicable to my day-to-day work as a software developerthan I ever thought it would be.<\/p><p>An MBA mindset helps developers solve problems up and down a company\u2019smanagement structure. It gives you the tools to assist the management team inmaking tradeoff decisions between projects, and also more perspective on whythat piece of code you\u2019re writing is so important to the company\u2019s success.<\/p><h2 id=\"speaker-information\">Speaker Information <a href=\"#speaker-information\">\ud83d\udd17<\/a><\/h2><p>Kevin utilizes the knowledge learned as a business school graduate every day asa software developer. As proof of how great at business he is, he\u2019s giving allof this information away: for free.<\/p><p>Kevin lives near Boston where he is a Software Developer at The Gnar Company.<\/p>"},{"title":"Enough Coverage To Beat The Band - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/enough-coverage-to-beat-the-band-proposal\/","pubDate":"Wed, 03 Aug 2022 21:26:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/enough-coverage-to-beat-the-band-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>The lights cut out. The crowd roars. It\u2019s time. The band takes the stage. They\u2019ve practiced the songs, particularly the <em>covers<\/em>. They\u2019ve sound checked the <em>coverage<\/em> of the speakers. They know the lighting rig has the proper colored gels <em>covering<\/em> the lamps. They\u2019re nervous, but they\u2019ve got it all <strong>covered<\/strong>.<\/p><p>Similarly, code coverage can give you confidence before your app performs on production and also tell you how live code is used (or not used). We\u2019ll cover how to leverage ruby\u2019s different coverage measurement techniques in concert to assist your crew and delight your audience.<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>This talk will be an explanation of the different modes ruby\u2019s <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.0\/libdoc\/coverage\/rdoc\/Coverage.html\">Coverage<\/a> module provides. It will use the theme of staging a concert as the through line. We\u2019ll progress through the band\u2019s setlist, where each song is one of the modes you can pass to <code>Coverage.start<\/code>.<\/p><p>For each mode, we will examine how it works and what it tells you. We\u2019ll examine the coverage results of code that models performing a concert as example scenarios where that code coverage mode yields actionable insights. While code coverage is frequently talked about in regards to an application\u2019s test suite, this talk will also explore how coverage can be used for instrumenting a running application in production.<\/p><h3 id=\"code-coverage-modes\">Code Coverage Modes <a href=\"#code-coverage-modes\">\ud83d\udd17<\/a><\/h3><p>Each mode answers a different question about the code that was run under coverage:<\/p><ul><li>Lines - how many times was each line executed?<\/li><li>Oneshot Lines - which lines were executed?<\/li><li>Branches - was this code path of a conditional executed?<\/li><li>Methods - was this method executed?<\/li><\/ul><h4 id=\"lines\">Lines <a href=\"#lines\">\ud83d\udd17<\/a><\/h4><p>This is the \u201cclassic\u201d implementation of providing coverage. Each relevant line, that is those that aren\u2019t things like empty lines or \u201cend\u201d statements, has a counter that is incremented each time the line is visited in code execution while coverage is running. At the conclusion, you will see how many times each line is executed.<\/p><h5 id=\"benefits\">Benefits <a href=\"#benefits\">\ud83d\udd17<\/a><\/h5><ul><li>This is the default mode for coverage.<\/li><li>Most of the time, this option will provide you with the information you want.<\/li><\/ul><h5 id=\"instructive-example\">Instructive Example <a href=\"#instructive-example\">\ud83d\udd17<\/a><\/h5><p>The band\u2019s guitar technician needs to place an order for guitar strings but needs to know how many to buy. Each guitar is restrung before each show; however, strings can break during the performance. Knowing the correct buffer to purchase is the unknown. We can\u2019t run out of strings, but we also can\u2019t be loading and unloading unnecessary equipment every night.<\/p><p>We already have one big box of strings that should last a while. We\u2019ll track how many strings break throughout the first 10 shows of the tour and use that number to place an order for the rest of the tour. In our code that powers this concert, there is a line in the method to pluck a guitar string that will break the string. We will run coverage over the first 10 shows in production to see how often that line is executed, and know how big of an order to place.<\/p><h4 id=\"oneshot-lines\">Oneshot Lines <a href=\"#oneshot-lines\">\ud83d\udd17<\/a><\/h4><p>Similar to Lines Coverage, this also documents that a relevant line was executed while coverage was running. However, it\u2019s a binary report of whether it was executed or not. It will not tell you how often. This may be sufficient in many cases, and comes with the benefit of being more performant every subsequent time a particular line of code is executed under coverage.<\/p><h5 id=\"benefits-1\">Benefits <a href=\"#benefits-1\">\ud83d\udd17<\/a><\/h5><ul><li>Oneshot provides you with nothing more than if a line of application code is executed in a test suite.<\/li><li>As long as being constrained to knowing if something ran or not, and not knowing how often, is sufficient, Oneshot Lines Coverage provides the same feedback as Lines Coverage with better performance.<\/li><\/ul><h5 id=\"instructive-example-1\">Instructive Example <a href=\"#instructive-example-1\">\ud83d\udd17<\/a><\/h5><p>This band uses a lot of synthesizers and keyboards in their music, and the different sounds that they can make are stored as \u201cpatches\u201d. These are stored in memory on these instruments, and the memory storage is finite. The band has been booked to play a festival, which will be great for exposure to a larger audience. However, because of the number of other acts performing and the shortened time to set up their equipment, they can\u2019t load in and set up all of their synthesizers.<\/p><p>No single synthesizer they have contains enough memory to store all their patches. However, they can\u2019t play these songs without the right patches. It will very obviously sound wrong, both to them and the audience. Whether the sound or effect is used once or a million times, it\u2019s equally vital to have it available for the live show.<\/p><p>To find out what patches they need for this one show, they run through the entire festival setlist beforehand, keeping track of whether a patch was used. After all the songs have been played, any patches that are used will be loaded on the fewest number of synthesizers possible. The other patches can be safely left off the instruments used for the festival.<\/p><h4 id=\"branches\">Branches <a href=\"#branches\">\ud83d\udd17<\/a><\/h4><p>Branches Coverage tracks execution of different conditional paths and documents how often those different paths are run. The unique benefit that this provides over Line Coverage is in conditionals that execute multiple code paths in a single line, such as ternary statements. You may have a part of that conditional that\u2019s never run or tested, but you wouldn\u2019t know that if you\u2019re relying on Lines Coverage alone.<\/p><h5 id=\"benefits-2\">Benefits <a href=\"#benefits-2\">\ud83d\udd17<\/a><\/h5><ul><li>It provides a different frame of reference than Lines Coverage, which ends up being either coarser or more granular than Lines coverage in different situations.<\/li><li>For conditionals that lay out multiple code paths on a single line, this provides feedback on their individual execution where Lines Coverage only considers whether any part of the line was run.<\/li><li>When interested in conditionals, and only conditionals, it has less noise than Lines Coverage.<\/li><\/ul><h5 id=\"instructive-example-2\">Instructive Example <a href=\"#instructive-example-2\">\ud83d\udd17<\/a><\/h5><p>The band is rehearsing a new cover song to add to the setlist; however, something isn\u2019t right. They can\u2019t pinpoint what it is, so they haven\u2019t played the song live for an audience yet, but they keep working at it. Finally, taking a very analytical approach to one of their rehearsals, they find it - one of the vocal effects they use isn&rsquo;t being applied correctly.<\/p><p>This effect is set in a ternary statement in the method that runs the music for each of the choruses for this song. Using Lines Coverage alone, it appears in tests (their rehearsal) that they are getting to that part of the code, or song; however, when tracking Branches Coverage they realize they\u2019re never setting the effect to be the value it should through all the choruses.<\/p><h4 id=\"methods\">Methods <a href=\"#methods\">\ud83d\udd17<\/a><\/h4><p>Methods Coverage brings the granularity of Lines Coverage up to a coarser grain. Rather than tracking individual lines, it\u2019s concerned with whether a particular method is executed. It can be a 10 line method where the first line is the only line ever executed. Methods Coverage will still consider that as executed the same as a 20 line method where each line is executed.<\/p><h5 id=\"benefits-3\">Benefits <a href=\"#benefits-3\">\ud83d\udd17<\/a><\/h5><ul><li>This has a targeted focus to be able to answer a more specific question - is this method executed? - with easier to process feedback.<\/li><\/ul><h5 id=\"instructive-example-3\">Instructive Example <a href=\"#instructive-example-3\">\ud83d\udd17<\/a><\/h5><p>The lead singer\u2019s main spotlight is cutting out at times during the set, which makes for a less than optimal experience for everyone involved. The engineer\u2019s belief is that many of the venues don\u2019t have the power capacity to handle the needs of the show, and unfortunately, the most obvious spotlight is what\u2019s losing power.<\/p><p>This tour features an extensive stage production with many screens, lighting rigs, and smoke machines. Unfortunately, as the setlist has progressed throughout the tour, the crew isn\u2019t certain how much of it is being used on a night-to-night basis. However, each of those mechanisms are plugged in for every show, drawing power that could be going towards the primary spotlight.<\/p><p>Each lighting rig is triggered by calling a different method in our code that powers our concert. Given that the band has found their groove with a relatively stable setlist from night to night, the crew will instrument the code for an entire show to see which methods are triggered and which aren\u2019t. Those that aren\u2019t can be deleted - which means they can be unplugged, meaning more power for the spotlight.<\/p><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>Code coverage is a topic that often seems to be applied in a narrow sense. In discussions I\u2019m involved in, code coverage tends to mean whether there are tests that execute a given line of application code. This is used as a proxy for code quality, or as a heuristic that can be used to measure the health of a project.<\/p><p>However, I see less discussion on using coverage in production for instrumentation, or using these different modes in any context. Moreover, speaking to my own experience, I didn\u2019t even know there was a Coverage module for a long time as a rubyist, never mind knowing that it provides these multiple modes of tracking and reporting coverage.<\/p><p>My goal with this talk is to expand upon potential use cases of coverage that go beyond measuring how many lines of application code a test suite runs while introducing and explaining the different mechanisms ruby provides to track code coverage.<\/p><h2 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h2><p>Kevin lives near Boston, where he is a Software Developer at The Gnar Company. He\u2019s doing the best he can to cope with a year of concerts that could have been.<\/p>"},{"title":"Fake It While You Make It - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/fake-it-while-you-make-it-proposal\/","pubDate":"Wed, 03 Aug 2022 21:25:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/fake-it-while-you-make-it-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>We all write code to interface with external systems, like a web service or a message queue. Can you confidently write tests without requiring the system as a dependency? How can you shield users of your code from the inner workings of the interface? Explore one attempt to answer these questions.<\/p><p>There&rsquo;s no shortage of tools at your disposal to solve these problems. This talk will introduce some available options, provide guidance on when one approach may be more appropriate than another, and discuss how to use these tools together to ease the testing process.<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>This talk will follow one effort to test interactions with an external dependency that evolved over time, as the nature of the interaction became clearer, and as the complexity of the work around the dependency increased.<\/p><h3 id=\"testing-strategies\">Testing Strategies <a href=\"#testing-strategies\">\ud83d\udd17<\/a><\/h3><p>In the process of testing against the dependency, a variety of tools were used, each of which will be presented with a sample implementation, after which I will discuss their benefits and challenges.<\/p><h4 id=\"direct-interaction-with-dependency\">Direct Interaction with Dependency <a href=\"#direct-interaction-with-dependency\">\ud83d\udd17<\/a><\/h4><h5 id=\"benefits\">Benefits <a href=\"#benefits\">\ud83d\udd17<\/a><\/h5><ul><li>Confidence that your code will work the same in your test as in production.<\/li><li>Lowest barrier to entry.<\/li><\/ul><h5 id=\"challenges\">Challenges <a href=\"#challenges\">\ud83d\udd17<\/a><\/h5><ul><li>May negatively affect the performance of the test suite.<\/li><li>Can be non-deterministic, depending on the nature of the dependency.<\/li><li>Subject to any constraints the dependency may impose upon your system, such as an HTTP API that rate limits you after a certain number of requests.<\/li><\/ul><h5 id=\"when-to-use\">When To Use <a href=\"#when-to-use\">\ud83d\udd17<\/a><\/h5><ul><li>When unfamiliar with the dependency.<\/li><li>When exploring new features or functionality.<\/li><\/ul><h4 id=\"stubbing-responses\">Stubbing Responses <a href=\"#stubbing-responses\">\ud83d\udd17<\/a><\/h4><h5 id=\"benefits-1\">Benefits <a href=\"#benefits-1\">\ud83d\udd17<\/a><\/h5><ul><li>Promotes deterministic behavior in the response of the dependency for your test.<\/li><li>Allows for the test to succeed without the need for the dependency at all, limiting the performance overhead of the tests on the test suite.<\/li><\/ul><h5 id=\"challenges-1\">Challenges <a href=\"#challenges-1\">\ud83d\udd17<\/a><\/h5><ul><li>Must know the response structure.<\/li><li>Must ensure that the structure and data returned continues to mirror the reality of what the actual dependency provides.<\/li><\/ul><h5 id=\"when-to-use-1\">When To Use <a href=\"#when-to-use-1\">\ud83d\udd17<\/a><\/h5><ul><li>When you have a stable interface.<\/li><li>When the response surface itself is small, to mitigate verbosity in your test itself.<\/li><\/ul><h4 id=\"building-a-fake\">Building a Fake <a href=\"#building-a-fake\">\ud83d\udd17<\/a><\/h4><h5 id=\"benefits-2\">Benefits <a href=\"#benefits-2\">\ud83d\udd17<\/a><\/h5><ul><li>Provides a full-stack, complete test interaction.<\/li><li>Limited noise in the test itself, in terms of setup verbosity.<\/li><li>Allows you to build in as much or as little complexity as you need to serve the needs of your testing scenarios in an isolated, reusable location.<\/li><\/ul><h5 id=\"challenges-2\">Challenges <a href=\"#challenges-2\">\ud83d\udd17<\/a><\/h5><ul><li>Must ensure that the structure and data returned continues to mirror the reality of what the actual dependency provides.<\/li><li>Must consider how to test and verify that the implementation of your fake is performing as expected.<\/li><\/ul><h5 id=\"when-to-use-2\">When To Use <a href=\"#when-to-use-2\">\ud83d\udd17<\/a><\/h5><ul><li>When you need confidence in the communication mechanisms or protocols themselves.<\/li><li>When your test requires a multi-step interaction with the dependency, particularly where the dependency may need to store state, such as testing an OAuth handshake.<\/li><\/ul><h4 id=\"fixture-data\">Fixture Data <a href=\"#fixture-data\">\ud83d\udd17<\/a><\/h4><h5 id=\"benefits-3\">Benefits <a href=\"#benefits-3\">\ud83d\udd17<\/a><\/h5><ul><li>Provides a truthful representation of an interaction at a moment in time.<\/li><li>Provides a complete picture of a full response.<\/li><li>Requires the dependency only long enough to capture the fixture data.<\/li><\/ul><h5 id=\"challenges-3\">Challenges <a href=\"#challenges-3\">\ud83d\udd17<\/a><\/h5><ul><li>Can act as a mystery guest, where it\u2019s not clear where the data came from or why the test is responding in that way, given that the data is in a different location and not obvious within the test itself.<\/li><li>Given that the fixture is a representation at a particular moment in time, it\u2019s not guaranteed that the dependency continues to react with the same request in the same way.<\/li><li>Requires periodic access to the system to continually refresh these snapshots.<\/li><\/ul><h5 id=\"when-to-use-3\">When To Use <a href=\"#when-to-use-3\">\ud83d\udd17<\/a><\/h5><ul><li>When you need a complete response.<\/li><li>When your dependency is accessible for the generation and refreshing of this data.<\/li><li>When you must limit your impact on the dependency itself, such as not continually creating a new instance of a resource via a POST action to an HTTP API on an actual system every time the test is run.<\/li><\/ul><h3 id=\"architecture-learnings-from-this-investigation\">Architecture Learnings from this Investigation <a href=\"#architecture-learnings-from-this-investigation\">\ud83d\udd17<\/a><\/h3><p>Isolating the interaction with the dependency will be a main takeaway from this exploration. The focus will be the benefits that this provides from a testing perspective, as well as general extensibility towards the overall architecture of your application.<\/p><p>Finally, once the full public API of the system is defined and it&rsquo;s ready to be integrated as part of a larger system or shared with the world as a gem, we will discuss creating a &ldquo;test mode&rdquo; for your component. A great example of this is Sidekiq&rsquo;s <a href=\"https:\/\/github.com\/mperham\/sidekiq\/wiki\/Testing\">testing mode<\/a>. This will cover tips for how to build this functionality, how to expose it to consumers of your component, and the challenges associated with creating and maintaining this feature.<\/p><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>Interfacing with external systems has always been considered a challenging problem. It&rsquo;s also becoming a more common task, as we continue to interconnect with different systems. Microservice architectures make this even more pronounced, as we orchestrate many small, separate systems together to serve as the backbone of our overall technology solution.<\/p><p>Much of my career has been spent identifying, implementing, and maintaining interfaces. My goal is always to make it easy for other developers to interact with the system, and provide them with the information they need to complete their task without needing to be overwhelmed with technical details. I also developed, and continue to maintain, a gem that provides functionality to test interactions with another gem without the need for mocking or stubbing. I&rsquo;ve used that experience to inform my choices to ease testing of other interfaces I continue to create. The goal of this talk will be to share how a variety of testing practices can be used in concert when working with external dependencies.<\/p><h2 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h2><p>Kevin spent more time investigating, refactoring, and rewriting tests for the project that prompted this talk than he did writing the actual implementation.<\/p><p>Kevin lives near Boston, where he is a Software Developer at The Gnar Company.<\/p>"},{"title":"Don\u2019t Hang Me Out To DRY - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/dont-hang-me-out-to-dry-proposal\/","pubDate":"Wed, 03 Aug 2022 21:24:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/dont-hang-me-out-to-dry-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>Close your eyes and imagine the perfect codebase to work on. I bet you\u2019ll say it has complete test coverage. It\u2019s fully-optimized, both in terms of performance and architectural design. And, of course, it contains only DRY code. Surely we can all agree that this is an aspirational situation. But&hellip;do we <strong>really<\/strong> want that?<\/p><p>Don\u2019t get me wrong; these qualities are all beneficial. However, if we also think we should value everything in moderation, when should we push back on these ideals? What problems can they introduce? Let\u2019s talk about the exceptions to some of the \u201crules\u201d we all hold dear.<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>This talk will not argue against any of the principles of adequate test coverage, optimized code, or DRY code in the broad sense. The intention is not to dissuade the audience from using any of these rules, principles, or guidelines. They\u2019re all incredibly valuable, and we, as craftspeople, are better off for having them overall. This talk will discuss the ways these important ideals can fall short in specific ways and what alternatives are available in those situations.<\/p><p>The talk will initially introduce each of the principles, talking about why it\u2019s important and what benefits it provides. Code examples will be used to illustrate how we can write code or architect software solutions that adhere to these rules. Those examples will then be used to demonstrate some shortcomings of the principle, or situations where following the standard could have negative consequences. Along with identifying some of the downsides, possible solutions and alternatives will be suggested.<\/p><h2 id=\"outline\">Outline <a href=\"#outline\">\ud83d\udd17<\/a><\/h2><h3 id=\"full-test-coverage\">Full Test Coverage <a href=\"#full-test-coverage\">\ud83d\udd17<\/a><\/h3><h4 id=\"shortcomings\">Shortcomings <a href=\"#shortcomings\">\ud83d\udd17<\/a><\/h4><ul><li>100% test coverage does not mean all code paths are fully exercised, just that all lines are hit at least once in the execution of the test suite.<\/li><li>Testing every line of code can have costs that are felt initially in the time to conceive of the tests and the test strategy. However, the larger costs are felt over time if it leads to a long test suite run time, flakey or inconsistent tests, and a large suite that needs to be continually maintained as both the technologies and requirements of the application change.<\/li><\/ul><h4 id=\"alternatives\">Alternatives <a href=\"#alternatives\">\ud83d\udd17<\/a><\/h4><ul><li>Consider the value and impact of a test before committing it to the mainline branch of your repository.<\/li><li>Avoid treating code coverage solely as a vanity metric to be increased and maintained at all costs. Instead, consider trends over a long period or more specific information than the overall coverage number, such as what units are less thoroughly tested and why.<\/li><li>Listen to your tests when adding features to help guide the design and implementation. Listen to your overall test suite to aid in developer happiness and ergonomics. Is that flakey test pulling its weight? Do your longest-running tests add value commensurate with the time they tack on to every CI run?<\/li><\/ul><h3 id=\"dry-code\">DRY Code <a href=\"#dry-code\">\ud83d\udd17<\/a><\/h3><h4 id=\"shortcomings-1\">Shortcomings <a href=\"#shortcomings-1\">\ud83d\udd17<\/a><\/h4><ul><li>DRY code may introduce \u201cmystery guests\u201d in the form of variables that you don\u2019t know where they came from, or state that is difficult to understand how it occurred because it\u2019s decoupled from its caller in a way that obscures the work that\u2019s done.<\/li><li>Not repeating yourself can lead to premature optimizations or abstractions that end up saddling the codebase with a difficult-to-change architecture when it\u2019s discovered that use cases aren\u2019t as similar as initially thought.<\/li><\/ul><h4 id=\"alternatives-1\">Alternatives <a href=\"#alternatives-1\">\ud83d\udd17<\/a><\/h4><h5 id=\"damp-code-descriptive-and-meaningful-phrases\">DAMP code (Descriptive and Meaningful Phrases) <a href=\"#damp-code-descriptive-and-meaningful-phrases\">\ud83d\udd17<\/a><\/h5><p>This is particularly helpful in tests. If common setup about the state of the world has been extracted into a central location, it may be more difficult to tell the story of the test and understand why the current state is important or relevant to the situation being tested. Repeating this setup can draw attention to what\u2019s relevant in the test and why.<\/p><p>If it\u2019s critical to the value of your test that a struct has an attribute called \u201cfoo\u201d which returns \u201ctrue\u201d, but that struct is defined in a helper method at the very top of the test file, it can be difficult to parse why the test is reacting the way it is. Repeating the definition of that struct closer to the exercise section of your test can improve clarity and understanding.<\/p><h5 id=\"wet-code-write-everything-twice\">WET code (Write Everything Twice) <a href=\"#wet-code-write-everything-twice\">\ud83d\udd17<\/a><\/h5><p>Explicitly duplicating functionality or sections of code for additional use, even when you feel confident it\u2019s the same, can have many benefits. If you\u2019re explicitly rewriting it, rather than a copy and paste operation, you can re-explore the implementation and may discover a different solution to the same problem. Even without rewriting it, having the same code multiple places can help you move faster initially without needing to consider the various call sites that a DRY solution may require. It can validate your assumptions regarding just how similar these use cases really are. This could prevent an incorrect abstraction from being chosen, and the two data points can help better inform the design decisions once that third occurrence of the functionality comes up.<\/p><h3 id=\"optimized-code\">Optimized Code <a href=\"#optimized-code\">\ud83d\udd17<\/a><\/h3><h4 id=\"shortcomings-2\">Shortcomings <a href=\"#shortcomings-2\">\ud83d\udd17<\/a><\/h4><ul><li>Code that is preemptively performance-tuned may or may not be necessary or accurate. Without the data and benchmarking, under load, to illustrate the performance impact, any improvements made in service of performance are done so based on conjecture. This could lead to unnecessary time in developing the believed performance benefit or may even introduce subtle bugs.<\/li><li>Calling back to choosing the wrong abstraction when focusing on delivering DRY code, working to present the perfect design pattern or architecture to serve the problem at hand can handcuff future feature development while also having an up-front cost to initially introduce the pattern for the first use case.<\/li><\/ul><h4 id=\"alternatives-2\">Alternatives <a href=\"#alternatives-2\">\ud83d\udd17<\/a><\/h4><ul><li>Ensure the <a href=\"http:\/\/wiki.c2.com\/?MakeItWorkMakeItRightMakeItFast\">initial focus<\/a> is on delivering code that solves the problem. Then layer on performance improvements aided by benchmarks and observations from APM or another instrumentation tooling, should it prove to be called for.<\/li><li>Certainly reach for design patterns and well-known architectural principles when the opportunity presents itself. But, consider how one might back out of such an introduction should it turn out that the choice was premature - or hold off on introducing it entirely until it\u2019s more clear or there\u2019s sufficient churn in that area of the codebase to warrant the attention.<\/li><\/ul><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>Quality code is highly valued, but difficult to measure. And though we, as a community, have many ideals, they don\u2019t all fit in every scenario. You may be hard-pressed to find a Ruby developer who might take the position that testing is bad, or DRY code is problematic. Most times, they\u2019d be right not to take that stance. However, writing our code in service of these ideals without considering their implications has costs that may not be explored or understood at the time.<\/p><p>I\u2019ve had the fortune (or misfortune) of seeing the unintended consequences of adherence to some of these rules on a team, codebase, and a project. Many of these principles can be easy to spot and follow, leading to comments that are easy to make in a pull request, changes that are easy to make, and refactorings that feel straightforward and successful enough in that time. The downsides to these changes can be more difficult to identify.<\/p><p>I will share examples of where, particularly in the long-term, following these rules can have negative effects in specific situations, and propose suggestions to address those effects or prevent them from occurring in the first place.<\/p><h2 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h2><p>Kevin lives near Boston, where he is a Software Developer at The Gnar Company. Though he believes he holds strong opinions, his most frequent response to a question starts with, \u201cit depends.\u201d<\/p>"},{"title":"I know I can, but should I? Evaluating Alternatives - The Proposal","link":"https:\/\/kevinjmurphy.com\/posts\/evaluating-alternatives-proposal\/","pubDate":"Wed, 03 Aug 2022 21:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/evaluating-alternatives-proposal\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>You <strong>can<\/strong> use a hammer to drive a screw into wood, but I\u2019d recommend a screwdriver. Why? And when is a hammer the better option? This talk will propose a framework to use when comparing alternative technical choices. I won\u2019t decide for you, but will leave you with a structure to apply in your decision-making process.<\/p><p>The ruby toolbox is vast. While Rails provides a default experience, it leaves plenty of room for alternatives. In learning how to do something, you may uncover different ways to accomplish the same goal. Determine which tool fits best in your situation with these lessons.<\/p><h2 id=\"details\">Details <a href=\"#details\">\ud83d\udd17<\/a><\/h2><p>The Rails Doctrine frequently speaks to the breadth of options available at your disposal within the framework. There are many <a href=\"https:\/\/rubyonrails.org\/doctrine\/#provide-sharp-knives\">sharp knives<\/a> in ruby that, when used judiciously, can facilitate a great result for your product, your team, and your code. However, knowing in which situations to reach for one tool, technology, or architecture when the framework supports <a href=\"https:\/\/rubyonrails.org\/doctrine\/#no-one-paradigm\">many paradigms<\/a> is difficult. Having the option to <a href=\"https:\/\/rubyonrails.org\/doctrine\/#omakase\">make substitutions<\/a> is liberating, but imposing.<\/p><p>Some groups or situations may demand rigorous up-front <a href=\"https:\/\/en.wikipedia.org\/wiki\/Analysis_of_Alternatives\">evaluations<\/a> prior to determining a solution. While those exercises can inform our choices, a lighter-weight approach is more appropriate in most decisions we make on a daily basis as developers. When presented with multiple options that will solve a problem, the following criteria are helpful to consider:<\/p><ul><li>What impact does this have on the code, the product, and the team?<\/li><li>Has the team encountered this before? What was the result?<\/li><li>What cost and risk will be incurred by introducing this solution?<\/li><li>How will this be maintained over time?<\/li><\/ul><p>Not all of these questions may be applicable in all situations, and the relative weight of one over the others will also vary, depending on the context of the team or the problem. These questions will be exercised in this talk to reinforce their value and applicability in making practical decisions about daily programming choices such as:<\/p><ul><li>Should I use an ActiveRecord callback, or wrap this logic in another method?<\/li><li>How, if at all, should this change be tested?<\/li><li>Can I write this myself, or use an external library?<\/li><li>Do I expose default timestamp values to users or add a new attribute?<\/li><\/ul><h3 id=\"outline\">Outline <a href=\"#outline\">\ud83d\udd17<\/a><\/h3><ol><li>Introduction<\/li><li>\u201cTraditional\u201d Project Management Approach<\/li><\/ol><ul><li>Methodology<ul><li>Present a detailed, analytical approach before work is done.<\/li><li>Identify all criteria and factors applicable to the project (cost, effectiveness, risk, etc.), with relative weights of importance.<\/li><li>Score all identified alternatives on criteria, multiply by weight, compute final score.<\/li><li>Implement highest-scoring option.<\/li><\/ul><\/li><li>Limitations<ul><li>Significant upfront investment in time.<\/li><li>Assumes perfect knowledge prior to execution.<\/li><li>Inflexible to new information or constraints as the project evolves.<\/li><\/ul><\/li><li>Merits<ul><li>Requires thoughtful and thorough consideration of all options.<\/li><li>Provides cover and justification for decisions made.<\/li><li>Rallies all stakeholders around a consensus determination.<\/li><\/ul><\/li><\/ul><ol start=\"3\"><li>Evolving Traditional Approach for Daily\/Common Decisions<\/li><\/ol><ul><li>What impact will this have on the project?<ul><li>Research, prototype, or spike out how well this resolves the issue at hand.<\/li><li>Identify how your supporting team may react to the change. Consider their past experience, strongly-held beliefs, and ability to build on or maintain this.<\/li><li>Contemplate what, if any, standard this sets for future similar problems. Communicate, if necessary, not only for the current team but future contributors, the lens through which this should be evaluated for upcoming features.<\/li><li><em>Example<\/em>: Should I use an ActiveRecord callback, or wrap this logic in another method?<\/li><\/ul><\/li><li>Has the team encountered this before?<ul><li>Determine what institutional knowledge can be brought to bear to short-circuit the decision-making process.<\/li><li>Evaluate the relevance and applicability of any past decisions or solutions to similar problems.<\/li><li>Identify what, if any, conventions or standards should be adhered to within this codebase or organization.<\/li><li><em>Example<\/em>: How, if at all, should this change be tested?<\/li><\/ul><\/li><li>What cost and risk will be incurred by introducing this solution?<ul><li>Recognize that \u201ccorrectness\u201d is not the only consideration in delivering features.<\/li><li>Take into account contextual pressures. Keep in mind what amount of time or money is available to address this problem.<\/li><li>Expand the evaluation of cost and risk beyond the initial implementation and through the entire lifecycle of the product.<\/li><li><em>Example<\/em>: Can I write this myself, or use an external library?<\/li><\/ul><\/li><li>How will this be maintained over time?<ul><li>Speculate on how extensible or malleable this implementation is to future requirements or features.<\/li><li>Identify who will need to be involved in the long-term viability of this solution and solicit their input.<\/li><li>Determine your own appetite to continue the use of this work as a proxy, if nothing else is available.<\/li><li><em>Example<\/em>: Do I expose default timestamp values to users or add a new attribute?<\/li><\/ul><\/li><\/ul><ol start=\"4\"><li>Conclusion<\/li><\/ol><h2 id=\"pitch\">Pitch <a href=\"#pitch\">\ud83d\udd17<\/a><\/h2><p>For many, the initial excitement of learning to program is in creating something. Having a computer perform a task can feel like magic. That rush can get you far, resulting in successful projects, companies, and careers.<\/p><p>At some point, understanding different ways to solve the same problem becomes compelling, either due to intellectual curiosity, organizational need, or other circumstances. However, ingesting all of the alternatives that exist can be exhausting, leading to analysis paralysis. Having a structure in place to help implement these decisions is crucial with the current number of options available to us as developers.<\/p><p>As a consultant, I\u2019m exposed to a number of organizations looking to solve difficult problems. Each of them has different constraints that influence one choice over another. Much of my job involves understanding the pressures facing the team and proposing solutions that will not only solve the problem, but do so in a way that best meets the needs and addresses the goals of the group.<\/p><h2 id=\"bio\">Bio <a href=\"#bio\">\ud83d\udd17<\/a><\/h2><p>Kevin lives near Boston, where he is a Software Developer at The Gnar Company. As a consultant, evaluating technical alternatives and proposing solutions is an important part of his job.<\/p>"},{"title":"Request Spec Realistic Error Response","link":"https:\/\/kevinjmurphy.com\/posts\/request-spec-realistic-error-response\/","pubDate":"Sun, 17 Jul 2022 07:33:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/request-spec-realistic-error-response\/","description":"<h2 id=\"happy-request-specs\">Happy Request Specs <a href=\"#happy-request-specs\">\ud83d\udd17<\/a><\/h2><blockquote><p>Let\u2019s build some happy little trees.&ndash; <a href=\"https:\/\/parade.com\/1215163\/kelseypelzer\/bob-ross-quotes\/\">Bob Ross<\/a><\/p><\/blockquote><p>When using RSpec, you can create a <a href=\"https:\/\/relishapp.com\/rspec\/rspec-rails\/docs\/request-specs\/request-spec\">request spec<\/a> to &ldquo;drive behavior through the full stack&rdquo;.<\/p><p>I use request specs as my highest-level acceptance test for API endpoints. We&rsquo;re building a system that allows users to place predefined elements on a canvas of sorts. Let&rsquo;s build a test for placing a little tree on the canvas.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;creates a happy little tree&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> post little_trees_path,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">params<\/span>: { <span style=\"color:#e6db74\">tree<\/span>: { <span style=\"color:#e6db74\">disposition<\/span>: <span style=\"color:#e6db74\">&#34;happy&#34;<\/span>, <span style=\"color:#e6db74\">x<\/span>: <span style=\"color:#ae81ff\">200<\/span>, <span style=\"color:#e6db74\">y<\/span>: <span style=\"color:#ae81ff\">100<\/span> } }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response)<span style=\"color:#f92672\">.<\/span>to have_http_status(<span style=\"color:#e6db74\">:created<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We will also verify that we can find the tree.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;shows an existing tree&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> tree <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">FactoryBot<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">:tree<\/span>, <span style=\"color:#e6db74\">:little<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> get little_trees_path(tree)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response)<span style=\"color:#f92672\">.<\/span>to have_http_status(<span style=\"color:#e6db74\">:ok<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"testing-accidents\">Testing Accidents <a href=\"#testing-accidents\">\ud83d\udd17<\/a><\/h2><blockquote><p>We don\u2019t make mistakes, just happy little accidents.&ndash; <a href=\"https:\/\/parade.com\/1215163\/kelseypelzer\/bob-ross-quotes\/\">Bob Ross<\/a><\/p><\/blockquote><p>We also want to ensure the system responds as expected when users make happy accidents. Asking this endpoint for a tree that&rsquo;s not little is not found.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;does not find a large tree&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> tree <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">FactoryBot<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">:tree<\/span>, <span style=\"color:#e6db74\">:large<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> get little_trees_path(tree)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response)<span style=\"color:#f92672\">.<\/span>to have_http_status(<span style=\"color:#e6db74\">:not_found<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This test does not pass! Not because we&rsquo;re TDD-ing this and the implementation doesn&rsquo;t exist. The endpoint <em>does<\/em> respond as expected if you hit it from <a href=\"https:\/\/curl.se\/\">curl<\/a> or another way.<\/p><p>Our test raises an <code>ActiveRecord::RecordNotFound<\/code> exception. We know that Rails has <a href=\"https:\/\/kevinjmurphy.com\/posts\/wrapping-about-exceptional-behavior-in-rails\/#research\">special handling<\/a> to return a 404 status code in this case. However, the request spec still raises the exception.<\/p><h2 id=\"prior-art\">Prior Art <a href=\"#prior-art\">\ud83d\udd17<\/a><\/h2><blockquote><p>Anytime you learn, you gain.&ndash; <a href=\"https:\/\/parade.com\/1215163\/kelseypelzer\/bob-ross-quotes\/\">Bob Ross<\/a><\/p><\/blockquote><p>I didn&rsquo;t figure out how to resolve this on my own. I only came across a solution by <a href=\"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/\">searching<\/a> for others&rsquo; approaches. I found <a href=\"https:\/\/github.com\/rspec\/rspec-rails\/issues\/2024\">this issue<\/a> in <code>rspec-rails<\/code>&rsquo;s issue tracker. In there <a href=\"https:\/\/github.com\/mikegee\">Michael Gee<\/a> shares a workaround. The earliest reference to this approach that <strong>I<\/strong> can find is from <a href=\"https:\/\/eliotsykes.com\/2017\/03\/08\/realistic-error-responses\/\">Eliot Sykes<\/a> in 2017. If you&rsquo;re aware of an earlier reference to this, let me know!<\/p><h2 id=\"painting-a-realistic-picture\">Painting A Realistic Picture <a href=\"#painting-a-realistic-picture\">\ud83d\udd17<\/a><\/h2><blockquote><p>All you need to paint is a few tools, a little instruction, and a vision in your mind.&ndash; <a href=\"https:\/\/parade.com\/1215163\/kelseypelzer\/bob-ross-quotes\/\">Bob Ross<\/a><\/p><\/blockquote><p>To get the production behavior in our test, we need to turn off Rails&rsquo; <a href=\"https:\/\/www.honeybadger.io\/blog\/how-rails-fancy-exception-page-works\/\">fancy exception page<\/a>. Michael and Eliot share this method to do so:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> ErrorResponses<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">respond_without_detailed_exceptions<\/span><\/span><\/span><span style=\"display:flex;\"><span> env_config <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>application<span style=\"color:#f92672\">.<\/span>env_config<\/span><\/span><span style=\"display:flex;\"><span> original_show_exceptions <span style=\"color:#f92672\">=<\/span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> original_show_detailed_exceptions <span style=\"color:#f92672\">=<\/span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_detailed_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">:all<\/span><\/span><\/span><span style=\"display:flex;\"><span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_detailed_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">yield<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">ensure<\/span><\/span><\/span><span style=\"display:flex;\"><span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> original_show_exceptions<\/span><\/span><span style=\"display:flex;\"><span> env_config<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;action_dispatch.show_detailed_exceptions&#34;<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> original_show_detailed_exceptions<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We <a href=\"https:\/\/kevinjmurphy.com\/posts\/temporary-state-in-tests\/\">pass a block<\/a> to this method in our test. We store the initial values of the environment configuration, then set <code>show_exceptions<\/code> and <code>show_details_exceptions<\/code> to match production. After our block executes, we return them to their original value.<\/p><p>Note that prior to Rails 7.1, you&rsquo;ll want to set the value of <code>show_exceptions<\/code> to <code>true<\/code>. From 7.1 on, the configuration accepts a <a href=\"https:\/\/guides.rubyonrails.org\/configuring.html#config-action-dispatch-show-exceptions\">set of symbols<\/a> and using true or false is deprecated.<\/p><p>Let&rsquo;s update our test to use this method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;does not find a large tree&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> respond_without_detailed_exceptions <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> tree <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">FactoryBot<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">:tree<\/span>, <span style=\"color:#e6db74\">:large<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> get little_trees_path(tree)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response)<span style=\"color:#f92672\">.<\/span>to have_http_status(<span style=\"color:#e6db74\">:not_found<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now our test passes! We&rsquo;re returning a 404 and not raising an exception.<\/p><h2 id=\"using-your-tools\">Using Your Tools <a href=\"#using-your-tools\">\ud83d\udd17<\/a><\/h2><blockquote><p>You can do anything here \u2014 the only prerequisite is that it makes you happy.&ndash; <a href=\"https:\/\/www.pinotspalette.com\/logansquare\/blog\/pinot-state-of-mind\/bob-ross-quotes\">Bob Ross<\/a><\/p><\/blockquote><p>If we&rsquo;re using RSpec, we have another option, which again comes from <a href=\"https:\/\/eliotsykes.com\/2017\/03\/08\/realistic-error-responses\/\">Eliot<\/a>. We can use RSpec&rsquo;s <code>around<\/code> <a href=\"https:\/\/relishapp.com\/rspec\/rspec-core\/v\/2-0\/docs\/hooks\/around-hooks\">hook<\/a> to pass the entire test as the block to <code>respond_without_detailed_exceptions<\/code> by applying metadata.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> ErrorResponses<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">RSpec<\/span><span style=\"color:#f92672\">.<\/span>configure <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>config<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> config<span style=\"color:#f92672\">.<\/span>include self, <span style=\"color:#e6db74\">type<\/span>: <span style=\"color:#e6db74\">:request<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> config<span style=\"color:#f92672\">.<\/span>around(<span style=\"color:#e6db74\">realistic_error_responses<\/span>: <span style=\"color:#66d9ef\">true<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>example<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> respond_without_detailed_exceptions(<span style=\"color:#f92672\">&amp;<\/span>example)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>That allows us to rewrite our test as follows:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;does not find a large tree&#34;<\/span>, <span style=\"color:#e6db74\">:realistic_error_responses<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> tree <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">FactoryBot<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">:tree<\/span>, <span style=\"color:#e6db74\">:large<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> get little_trees_path(tree)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(response)<span style=\"color:#f92672\">.<\/span>to have_http_status(<span style=\"color:#e6db74\">:not_found<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"artistic-inspiration\">Artistic Inspiration <a href=\"#artistic-inspiration\">\ud83d\udd17<\/a><\/h2><blockquote><p>Don\u2019t forget to tell these special people in your life just how special they are to you.&ndash; <a href=\"https:\/\/parade.com\/1215163\/kelseypelzer\/bob-ross-quotes\/\">Bob Ross<\/a><\/p><\/blockquote><p>I cannot stress enough that this post wouldn&rsquo;t exist without the contributions of <a href=\"https:\/\/github.com\/mikegee\">Michael Gee<\/a> and <a href=\"https:\/\/eliotsykes.com\">Eliot Sykes<\/a>. I&rsquo;m thankful to them for sharing this solution. I&rsquo;ve used this approach in various codebases in the last few years. I&rsquo;ve shared this approach with many people (and now with you). This post serves to draw attention to their work.<\/p>"},{"title":"Designing A Dependency's Domain","link":"https:\/\/kevinjmurphy.com\/posts\/designing-dependency-domain\/","pubDate":"Sun, 19 Jun 2022 20:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/designing-dependency-domain\/","description":"<h2 id=\"sweathr\">Sweathr <a href=\"#sweathr\">\ud83d\udd17<\/a><\/h2><p>In our <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/\">last post<\/a> I introduced you to Sweathr. We&rsquo;ve revolutionized the sweater weather prediction landscape forever. We evaluated many ways to test the core business logic, which called out to a HTTP weather API.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Location<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">sweater_weather?<\/span><\/span><\/span><span style=\"display:flex;\"><span> uri <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">URI<\/span>(conditions_url_for_zip_code)<\/span><\/span><span style=\"display:flex;\"><span> result <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Net<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">HTTP<\/span><span style=\"color:#f92672\">.<\/span>get(uri)<\/span><\/span><span style=\"display:flex;\"><span> response <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">JSON<\/span><span style=\"color:#f92672\">.<\/span>parse(result)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> feels_like <span style=\"color:#f92672\">=<\/span> response<span style=\"color:#f92672\">.<\/span>dig(<span style=\"color:#e6db74\">&#34;current_observation&#34;<\/span>, <span style=\"color:#e6db74\">&#34;feelslike_f&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> feels_like<span style=\"color:#f92672\">.<\/span>to_f<span style=\"color:#f92672\">.<\/span>between?(<span style=\"color:#ae81ff\">55<\/span>, <span style=\"color:#ae81ff\">65<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Testing this code involved a fair amount of work. Any of the options had significant trade-offs we needed to weigh. Part of the reason testing this method was so much work is because it involves a dependency, the weather API. Part of the reason is, even though it&rsquo;s a small method, it&rsquo;s doing a lot of things.<\/p><p>In this post, we&rsquo;re going to decompose this method into three different units. Along the way, we&rsquo;ll talk about <em>why<\/em> we&rsquo;re doing this.<\/p><h3 id=\"api-client\">API Client <a href=\"#api-client\">\ud83d\udd17<\/a><\/h3><p>Let&rsquo;s start at the top. We&rsquo;re going to have a separate class that will handle HTTP communication with the weather API. We know we&rsquo;re going to have other code related to this API, so we&rsquo;ll put it in its own module.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Sweathr<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Weather<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Api<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_conditions<\/span>(<span style=\"color:#e6db74\">zip<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> uri <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">URI<\/span>(<span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>auth_uri<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">\/conditions\/q\/<\/span><span style=\"color:#e6db74\">#{<\/span>zip<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">.json&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">JSON<\/span><span style=\"color:#f92672\">.<\/span>parse(<span style=\"color:#66d9ef\">Net<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">HTTP<\/span><span style=\"color:#f92672\">.<\/span>get(uri))<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">auth_uri<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;https:\/\/real-weather-api.gov&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This class is solely focused on communicating with the dependency. There&rsquo;s subtle complexity here though. This class needs to determine how it&rsquo;s going to return the response. Right now, this is converting the response body JSON and returning the hash. This means that it&rsquo;s choosing to swallow the status code, headers, or other data the API is telling us. That&rsquo;s a choice, and it&rsquo;s isolated in this class. If we need access to other information, we know where we need to go to get it. Documenting, or iterating on, these types of decisions has a well-defined home.<\/p><h4 id=\"api-client-testing\">API Client Testing <a href=\"#api-client-testing\">\ud83d\udd17<\/a><\/h4><p>To test this, we can use any of the methods from our <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/\">last post<\/a>. However, I would advocate for one that, at least at some point, issues a real request to the API. That is the focus of this class, and as such, we want to make sure it&rsquo;s performing as expected. Using a fake or a stub here wouldn&rsquo;t give us a lot of confidence in testing our implementation.<\/p><p>Making a direct API call or using a fixture that&rsquo;s updated has an issue: determinism. Remember, this is a weather API - and the weather changes. We can&rsquo;t assert that it&rsquo;s sunny and 70 degrees and have it pass all the time.<\/p><p>Luckily, we don&rsquo;t have to. Instead, what we can focus on is the <em>structure<\/em> of the data, rather than the <em>contents<\/em>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;retrieves the current conditions&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> result <span style=\"color:#f92672\">=<\/span> api<span style=\"color:#f92672\">.<\/span>current_conditions(<span style=\"color:#e6db74\">zip<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(result)<span style=\"color:#f92672\">.<\/span>to <span style=\"color:#66d9ef\">include<\/span>(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;current_observation&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> a_hash_including(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;feelslike_f&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> a_kind_of(String)<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>It feels weird asserting we get a String in a language like Ruby sometimes, I&rsquo;ll grant you that. But, it doesn&rsquo;t matter to us what the temperature is. We want to validate that we can get back a temperature. We can do that by making sure it&rsquo;s nested inside the current observation with the key we expect.<\/p><p>This test is flexible where it can be - what the temperature is. It&rsquo;s also rigid where it has to be - we get a response and the data is in a format we expect.<\/p><p>This can serve as an early-warning system for upcoming components in our design. If this test fails, we&rsquo;ll need to change other areas of our domain.<\/p><p>Running this test as written <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/#direct-interaction\">directly interacts<\/a> with the dependency. This test will fail without an internet connection, or when the API is down. We could instead use a <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/#fixture\">fixture<\/a> that&rsquo;s regularly updated to mitigate that concern.<\/p><h3 id=\"data-representation\">Data Representation <a href=\"#data-representation\">\ud83d\udd17<\/a><\/h3><p>The next piece we&rsquo;re going to build will take in that response body and expose the data of interest to us. First things first, we need to give it a name. This data is telling us what the weather is in a place. We&rsquo;ll call it the current weather conditions.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Sweathr<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Weather<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">CurrentConditions<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(results)<\/span><\/span><span style=\"display:flex;\"><span> @results <span style=\"color:#f92672\">=<\/span> results<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">feels_like_f<\/span><\/span><\/span><span style=\"display:flex;\"><span> @results<span style=\"color:#f92672\">.<\/span>dig(<span style=\"color:#e6db74\">&#34;current_observation&#34;<\/span>, <span style=\"color:#e6db74\">&#34;feelslike_f&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_f<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>All we care about is knowing how hot or cold it feels outside. So, that&rsquo;s the one public method we&rsquo;ll expose. The API provides the number as a string, and we&rsquo;ll use this method to convert it to a float, so it&rsquo;s in a format we expect. This class provides us a location to expose more data that comes back should we need it in the future.<\/p><h4 id=\"data-representation-testing\">Data Representation Testing <a href=\"#data-representation-testing\">\ud83d\udd17<\/a><\/h4><p>We can unit test this using conventional and familiar means. There&rsquo;s no need to interface with the API at all.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>describe <span style=\"color:#e6db74\">&#34;#feels_like_f&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> it <span style=\"color:#e6db74\">&#34;gives the feels like temp in Fahrenheit&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> conditions <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">CurrentConditions<\/span><span style=\"color:#f92672\">.<\/span>new(json)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(conditions<span style=\"color:#f92672\">.<\/span>feels_like_f)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">55<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">5<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Because this is so targeted, we can also dig into very specific edge cases. We can explore how we handle data we might receive from the API without needing to interact with the API. How will this respond to receiving a string that can&rsquo;t convert to a float? What if there is no current observation key in the hash? We can provide a hash that meets those criteria and test that with minimal overhead.<\/p><h3 id=\"domain-module\">Domain Module <a href=\"#domain-module\">\ud83d\udd17<\/a><\/h3><p>We&rsquo;re going to tie these two components together in a module. This module will be our external API by which we consume this work.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Sweathr<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Weather<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">client<\/span><\/span><\/span><span style=\"display:flex;\"><span> @client <span style=\"color:#f92672\">||=<\/span> <span style=\"color:#66d9ef\">Api<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">current_conditions<\/span>(<span style=\"color:#e6db74\">zip_code<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">CurrentConditions<\/span><span style=\"color:#f92672\">.<\/span>new(<\/span><\/span><span style=\"display:flex;\"><span> client<span style=\"color:#f92672\">.<\/span>current_conditions(<span style=\"color:#e6db74\">zip<\/span>: zip_code)<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>As a consumer of this, I don&rsquo;t need to worry about knowing all the underlying classes involved. Let&rsquo;s see how we can use this in our existing <code>Location<\/code> class.<\/p><h2 id=\"usage\">Usage <a href=\"#usage\">\ud83d\udd17<\/a><\/h2><p>Interfacing with our weather API is now abstracted into a separate module. We can re-write our <code>Location#sweater_weather?<\/code> method to use it.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Location<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">sweater_weather?<\/span><\/span><\/span><span style=\"display:flex;\"><span> current <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">.<\/span>current_conditions(<span style=\"color:#e6db74\">zip_code<\/span>: @zip_code)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> current<span style=\"color:#f92672\">.<\/span>feels_like_f<span style=\"color:#f92672\">.<\/span>between?(<span style=\"color:#ae81ff\">55<\/span>, <span style=\"color:#ae81ff\">65<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>More of the focus in this method is now on the business logic. This method retrieves the current weather conditions, and determines if it feels like sweater weather. Accessing that information from the weather API is still important. But we don&rsquo;t need to understand that complexity when we&rsquo;re reading this method.<\/p><p>This separation gives us a new option for testing our interactions with the weather API. We&rsquo;ll explore that next.<\/p><h2 id=\"test-mode\">Test Mode <a href=\"#test-mode\">\ud83d\udd17<\/a><\/h2><h3 id=\"fake-client\">Fake Client <a href=\"#fake-client\">\ud83d\udd17<\/a><\/h3><p>Let&rsquo;s borrow a strategy from our prior post. We&rsquo;re going to build a <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/#fake\">fake<\/a>. But, we don&rsquo;t need to fake out the HTTP traffic. We&rsquo;re going to build a class that responds to the same methods as <code>Sweathr::Weather::Api<\/code>, but doesn&rsquo;t make a HTTP call.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FakeWeatherClient<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_conditions<\/span>(<span style=\"color:#e6db74\">zip<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;current_observation&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;feelslike_f&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> @results<span style=\"color:#f92672\">[<\/span>zip_code<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This client needs to responds to the same methods as the real client, so it can stand in for the original. Helpfully, it can also have extra behavior. We&rsquo;re going to add a method that allows us to specify the weather in a location.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FakeWeatherClient<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_condition<\/span>(<span style=\"color:#e6db74\">zip_code<\/span>:, <span style=\"color:#e6db74\">temp_f<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @results<span style=\"color:#f92672\">[<\/span>zip_code<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> temp_f<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now, we can control the weather anywhere in the world!<\/p><h3 id=\"test-mode-1\">Test Mode <a href=\"#test-mode-1\">\ud83d\udd17<\/a><\/h3><p>We need a way to specify that we want to use this fake client. We&rsquo;ll do so by making one change to our domain. We&rsquo;re going to add the ability to set the client we want to use to consume the weather API.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Sweathr<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Weather<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">client<\/span><\/span><\/span><span style=\"display:flex;\"><span> @client <span style=\"color:#f92672\">||=<\/span> <span style=\"color:#66d9ef\">Api<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">client<\/span><span style=\"color:#f92672\">=<\/span>(client)<\/span><\/span><span style=\"display:flex;\"><span> @client <span style=\"color:#f92672\">=<\/span> client<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We don&rsquo;t want to have to set this client in all our tests - and we certainly don&rsquo;t want to forget to set it back. We&rsquo;ll instead build a module that will take care of this for us.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">module<\/span> Sweathr<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Weather<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">module<\/span> Testing<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">enable!<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">.<\/span>client <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">FakeWeatherClient<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">yield<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">.<\/span>client <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Api<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This method expects a block, and for the <a href=\"https:\/\/kevinjmurphy.com\/posts\/temporary-state-in-tests\/\">duration of the block<\/a>, our fake client is the client it uses. At the conclusion of the block, it returns the client to one that will execute API requests.<\/p><p>Now in testing our <code>Location<\/code> class, we can define the state of the world (or at least the weather) in the setup of our test.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is time to break out the sweater&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Testing<\/span><span style=\"color:#f92672\">.<\/span>enable! <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Weather<\/span><span style=\"color:#f92672\">.<\/span>client<span style=\"color:#f92672\">.<\/span>add_condition(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">temp_f<\/span>: <span style=\"color:#e6db74\">&#34;56.0&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> location <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Location<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(location<span style=\"color:#f92672\">.<\/span>sweater_weather?)<span style=\"color:#f92672\">.<\/span>to be <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;re able to succinctly state that the temperature in zip code 02108 is 56 degrees at the top of the block. Then we exercise how our method under test responds to that scenario.<\/p><h2 id=\"domain-development\">Domain Development <a href=\"#domain-development\">\ud83d\udd17<\/a><\/h2><p>We needed to undertake a lot of work to allow for that test to exist. We decomposed a five line method into two classes and a module. We added in <em>another<\/em> module to test our weather API. And we drive those tests by a fake version of one of the new classes we wrote.<\/p><p>In doing so, we gained the ability to test our interaction with the dependency in greater detail. We have logical extension points for building in new functionality. We know where to add a method to consume new API endpoints. Or where to expose more data from the current weather conditions. We removed the need to handle HTTP traffic to test our application&rsquo;s business logic.<\/p><p>Was it worth it? For this contrived example, maybe not. As your usage of a dependency expands, having this separation has more merit. Defining a separate domain for the dependency clears up where the responsibility of the dependency ends and your usage of it begins. It makes it easier to reuse common interactions with the dependency. It provides clear seams to add in future functionality. It may be worth considering as you organize your next interaction with a new dependency.<\/p>"},{"title":"Testing Dependencies: Fake It While You Make It","link":"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/","pubDate":"Sun, 05 Jun 2022 21:15:06 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/","description":"<h2 id=\"sweathr\">Sweathr <a href=\"#sweathr\">\ud83d\udd17<\/a><\/h2><p>Is it feeling chilly outside where you live? Should you put a sweater on before you head outside? These questions used to be hard to answer. No more! Today we&rsquo;re going to be talking about Sweathr. This new, totally not fake, website exists to tell you if it&rsquo;s sweater weather where you live.<\/p><p>I debuted this revolutionary product at <a href=\"https:\/\/youtu.be\/iEfpAp2sqiw\">RailsConf 2020<\/a>. It communicates with a weather HTTP API to receive temperature information.<\/p><p>Here is the core business logic of our application. We define sweater weather to be from 55-65 degrees Fahrenheit.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Location<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">sweater_weather?<\/span><\/span><\/span><span style=\"display:flex;\"><span> uri <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">URI<\/span>(conditions_url_for_zip_code)<\/span><\/span><span style=\"display:flex;\"><span> result <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Net<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">HTTP<\/span><span style=\"color:#f92672\">.<\/span>get(uri)<\/span><\/span><span style=\"display:flex;\"><span> response <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">JSON<\/span><span style=\"color:#f92672\">.<\/span>parse(result)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> feels_like <span style=\"color:#f92672\">=<\/span> response<span style=\"color:#f92672\">.<\/span>dig(<span style=\"color:#e6db74\">&#34;current_observation&#34;<\/span>, <span style=\"color:#e6db74\">&#34;feelslike_f&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> feels_like<span style=\"color:#f92672\">.<\/span>to_f<span style=\"color:#f92672\">.<\/span>between?(<span style=\"color:#ae81ff\">55<\/span>, <span style=\"color:#ae81ff\">65<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;d like to test the <code>Location#sweater_weather?<\/code> method. Today, we&rsquo;re going to investigate four options to do so with the API call in the method.<\/p><ul><li><a href=\"#direct-interaction\">Direct Interaction<\/a><\/li><li><a href=\"#stub\">Stub<\/a><\/li><li><a href=\"#fake\">Fake<\/a><\/li><li><a href=\"#fixture\">Fixture<\/a><\/li><\/ul><h3 id=\"direct-interaction\">Direct Interaction <a href=\"#direct-interaction\">\ud83d\udd17<\/a><\/h3><p>As a starting point, we can write some tests that use the API directly.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is time to break out the sweater&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> location <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Location<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(location<span style=\"color:#f92672\">.<\/span>sweater_weather?)<span style=\"color:#f92672\">.<\/span>to be <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This will give us confidence that we are using the dependency correctly. Our test has full production parity. It will work the same in our test suite as it will deployed for our end users.<\/p><p>It comes with significant downsides though. The most glaring is that it&rsquo;s non-deterministic. This test succeeding <strong>literally depends<\/strong> on the weather in that location. It&rsquo;s likely to fail often - and not for actionable reasons. It will fail because it happens to be too hot or too cold outside at the given moment this test runs.<\/p><p>Tying this test to our dependency may be slow. We need to wait for the HTTP request and response to resolve before our test passes. We&rsquo;re also subject to the constraints of our dependency. With a HTTP API, we may have rate limits. That can become problematic when this test runs over and over again, locally and on CI. Our massive development team at Sweathr need to run the test suite often, and we may get rate limited.<\/p><p>You may think that this is a foolish approach. I&rsquo;ll agree, it&rsquo;s unlikelythe best choice in this example. Don&rsquo;t dismiss this strategy entirely though. You&rsquo;re probably using it in your own test suite! I&rsquo;m yet to see a test suite for a Rails app with persistence that doesn&rsquo;t interact with the backing store. We even write things we call <em>unit<\/em> tests which call out to the database (gasp). This is directly interacting with our dependency. And we&rsquo;ve largely decided, as a community, that we accept this. We are directly interacting with our dependency in our test.<\/p><h3 id=\"stub\">Stub <a href=\"#stub\">\ud83d\udd17<\/a><\/h3><p>We can avoid managing HTTP communication with our dependency. Instead, we&rsquo;ll stub out how we expect it to respond. In this case, we&rsquo;ll use <a href=\"https:\/\/github.com\/bblimke\/webmock\">webmock<\/a> to stub out a response at the HTTP layer.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is time to break out the sweater&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> api_response <span style=\"color:#f92672\">=<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">current_observation<\/span>: {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">feelslike_f<\/span>: <span style=\"color:#e6db74\">&#34;55.0&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> }<span style=\"color:#f92672\">.<\/span>to_json<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> stub_request(<span style=\"color:#e6db74\">:get<\/span>, url)<span style=\"color:#f92672\">.<\/span>to_return(<span style=\"color:#e6db74\">status<\/span>: <span style=\"color:#ae81ff\">200<\/span>, <span style=\"color:#e6db74\">body<\/span>: api_response)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> location <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Location<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(location<span style=\"color:#f92672\">.<\/span>sweater_weather?)<span style=\"color:#f92672\">.<\/span>to be <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This solves our determinism problem as well. In the world of this test, the weather is always 55 degrees at this location. It&rsquo;s faster than communicating over HTTP - it doesn&rsquo;t need HTTP at all. We can run this test in the middle of the woods with no internet connection and it&rsquo;ll pass without a problem.<\/p><p>There are still trade-offs we&rsquo;re making with this approach. First of all, we must know the response structure. We may need to <a href=\"#direct-interaction\">directly interact<\/a> with the dependency first to confirm the structure.<\/p><p>Just as important, we need to keep that structure up to date. If the weather API starts sending data in a different format, this test is going to continue to pass. We&rsquo;ll only find out there&rsquo;s an issue on production. That&rsquo;s less than great - I want my test to tell me there&rsquo;s a problem.<\/p><p>Stubbing out a response from your dependency in tests can be a great choice if you have a stable interface. You&rsquo;re avoiding using the dependency entirely. As such, you&rsquo;re taking on the responsibility to make sure it still reflects reality. That&rsquo;s easier to manage if the interface and data structure doesn&rsquo;t change.<\/p><h3 id=\"fake\">Fake <a href=\"#fake\">\ud83d\udd17<\/a><\/h3><p>Stubbing out the response may be too magical for you. A more tangible approach may be to build our own weather API. For test purposes we&rsquo;ll communicate with that API, rather than the real one.<\/p><p>Let&rsquo;s build our own weather API quick:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FakeWeather<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Sinatra<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Base<\/span><\/span><\/span><span style=\"display:flex;\"><span> get <span style=\"color:#e6db74\">&#34;\/api\/:key\/conditions\/q\/:zip_code.json&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> json <span style=\"color:#e6db74\">current_observation<\/span>: { <span style=\"color:#e6db74\">feelslike_f<\/span>: <span style=\"color:#e6db74\">&#34;56.0&#34;<\/span> }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can use the <code>FakeWeather<\/code> API rather than the real one using a tool like <a href=\"https:\/\/github.com\/thoughtbot\/capybara_discoball\">capybara_discoball<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is time to break out the sweater&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> location <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Location<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Capybara<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Discoball<\/span><span style=\"color:#f92672\">.<\/span>spin(<span style=\"color:#66d9ef\">FakeWeather<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>s<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> location<span style=\"color:#f92672\">.<\/span>endpoint_url <span style=\"color:#f92672\">=<\/span> s<span style=\"color:#f92672\">.<\/span>url<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> expect(location<span style=\"color:#f92672\">.<\/span>sweater_weather?)<span style=\"color:#f92672\">.<\/span>to be <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Within the <code>Capybara::Discoball.spin<\/code> block, we&rsquo;re communicating with <code>FakeWeather<\/code>. We&rsquo;re not issuing a request to the actual weather API. Because that fake always says it&rsquo;s 56 degrees, we still have a deterministic test. We can control how much flexibility we have in our fake. We can update it so that different locations get different temperatures if we&rsquo;d like. That complexity lives in the <code>FakeWeather<\/code> class. It&rsquo;s not contained within our individual tests that communicate with it.<\/p><p>Using the fake puts us back to communicating over HTTP. It happens to be communicating with a local HTTP server, but it&rsquo;s still using the protocol.<\/p><p>We still have the same consistency issues we had with stubbing. We need to make sure the responses our fake is providing mimic the responses of the real API and keep that up to date.<\/p><p>Using a fake may be beneficial when we need to test the communication mechanism, or protocol. It can also be useful when testing a multi-step interaction, like an OAuth flow. Rather than stubbing out each API call in that flow in a test, we can build a fake that has endpoints to respond to each of those requests.<\/p><h3 id=\"fixture\">Fixture <a href=\"#fixture\">\ud83d\udd17<\/a><\/h3><p>We have another option to capture a real interaction with the dependency. We won&rsquo;t need to build out a fake version of our dependency. We won&rsquo;t stub out a made-up response. We can store that interplay and reload it from that file for future runs. We call that file a fixture.<\/p><p>We&rsquo;ll use a tool called <a href=\"https:\/\/github.com\/vcr\/vcr\">vcr<\/a> to capture real HTTP requests and responses, storing and using them from then on. VCR&rsquo;s language and terminology leans heavily into its real-world analogue. If you don&rsquo;t know what a VCR is, it&rsquo;s a box we used to hook up to TVs to watch movies before DVD players. If you don&rsquo;t know what a DVD player is, it&rsquo;s a box we used to hook up to TVs to watch movies before streaming.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;is time to break out the sweater&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> location <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Sweathr<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Location<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">zip_code<\/span>: <span style=\"color:#e6db74\">&#34;02108&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">VCR<\/span><span style=\"color:#f92672\">.<\/span>use_cassette(<span style=\"color:#e6db74\">&#34;temp_needs_sweater&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(location<span style=\"color:#f92672\">.<\/span>sweater_weather?)<span style=\"color:#f92672\">.<\/span>to be <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The first time this test runs, it makes an actual HTTP request to the weather API. VCR saves the request and response information in a file called &ldquo;temp_needs_sweater.yml&rdquo;.<\/p><p>The next time this test runs, VCR uses the request and response from that YAML file. No HTTP requests.<\/p><p>This fixture is an absolutely true representation of an interaction with the dependency. It&rsquo;s true as of the moment in time it generates the fixture. Generating the fixture in a test and running the code in production should react the same.<\/p><p>Unfortunately, the time we generated that fixture quickly turns into the past. To ensure that correctness, we must update the fixture. Recording new fixtures lets us validate the continued correctness of our test. This is in contrast with the stub or the fake. Even though we <em>can<\/em> update them, we still need to take the action to refresh these fixtures.<\/p><h2 id=\"depending-on-tests\">Depending on Tests <a href=\"#depending-on-tests\">\ud83d\udd17<\/a><\/h2><p>We may be writing a test to serve a variety of needs. The dependency testing strategies listed above serve different needs.<\/p><p>When we&rsquo;re using the dependency for the first time, we want to make sure the code we&rsquo;re writing <em>works<\/em>. We&rsquo;re not familiar enough with it to guess how it will respond. We need the actual dependency. Interacting with it directly is the best approach to give us experience with it. It may prove to be good enough for a long time, like in the case of calling out to a database.<\/p><p>The overhead of accessing the dependency may prove to be too onerous. It may make the test unreliable. It may make the test too slow. We can stub out the response. This depends on how confidently we understand how the dependency will respond. It&rsquo;s helpful when we have some way to alert us that the assumption we&rsquo;ve made in our stub is now invalid.<\/p><p>A fake version of the dependency adds back some complexity. We need to generate the fake and communicate over the original protocol. The fake tests our usage of the dependency at a higher level. We isolate the complexity of the interactions inside the fake.<\/p><p>A fixture captures a real interaction with the dependency. We must refresh that interaction so it reflects the dependency&rsquo;s current reality.<\/p><p>There is no &ldquo;right&rdquo; answer to how to test against third-party dependencies. It&rsquo;s all <a href=\"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/\">trade-offs<\/a>, and it&rsquo;s up to us to determine which risks we&rsquo;re willing to accept for the greatest benefit.<\/p>"},{"title":"Searching for a Reason","link":"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/","pubDate":"Sun, 15 May 2022 07:00:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/","description":"<h2 id=\"search-driven-development\">Search-Driven Development <a href=\"#search-driven-development\">\ud83d\udd17<\/a><\/h2><p>We all only have so much working memory available in our brains. Developers may joke about spending their day composing search engine queries. The reason it&rsquo;s a joke is because of the truth behind it. Search-driven development is a reality.<\/p><p>Embracing that truth, I find it&rsquo;s important to consider <em>why<\/em> I&rsquo;m searching for something. It helps guide the path I&rsquo;ll take to resolve my query and helps frame the problem in a familiar context. In my own work, I&rsquo;ve found there are four main categories of reasons why I search. They all relate to how much experience I have with the problem I&rsquo;m working on.<\/p><h3 id=\"solve-direct-problems\">Solve Direct Problems <a href=\"#solve-direct-problems\">\ud83d\udd17<\/a><\/h3><p>When I&rsquo;m trying to do something for the first time, I may lack any context and perspective to have a starting point. I may get stuck mid-way through. And, being a novice at this task, I need to seek outside perspectives. I&rsquo;ll often build that up by searching for how others have accomplished the same task.<\/p><p>With a specific error that I&rsquo;m looking to fix, there&rsquo;s one thing I almost always do first. I <strong>copy and paste the error<\/strong> into a search engine. This is a high-leverage, low-effort move. It&rsquo;s unlikely you&rsquo;re the only person to have encountered this issue before. It requires validating that what you find are people experiencing the same problem. You must also determine for yourself if their solution works for you. However, it is still a go-to first step for me.<\/p><p>That may not be a great approach if the error is fairly generic. It may not yield results if a lot of the error message is about code specific to your application. In that case, I review the error message, and consider the context where it arrived. Search the README of the framework, dependency, or tool you&rsquo;re working with. Visit open and closed issues. Search their documentation. Find their forums. Use log messages to add to your search criteria.<\/p><p>You may conduct a thorough search using all these avenues and still not have an answer. You might start feeling frustrated, or angry. You might be doubting your abilities. When that creeps in, the most important thing I can recommend is that you stop.<\/p><p><strong>Stop.<\/strong><\/p><p>Move away and do something else for a few minutes, even if it&rsquo;s get a new glass of water. Give your brain some time to wander. Ponder what you&rsquo;re experiencing in a different physical location. Do something else to take your mind off of it.<\/p><p>When you&rsquo;re ready to regroup, re-review what the problem is and what steps you&rsquo;ve taken to address it. Look at your search history to see what you&rsquo;ve already looked for. Try to find any gaps in what you&rsquo;ve already explored.<\/p><p>At this point, remember that you don&rsquo;t have to solve this alone. You don&rsquo;t need to use the computer by yourself to solve the problem. If the computer could fix itself, we wouldn&rsquo;t have a job as developers! Reach out to your friends and your coworkers for help. Their fresh perspective may bring a quick resolution. They might suggestion other things to search for. They may help you with a different strategy to take.<\/p><h3 id=\"recall-details\">Recall Details <a href=\"#recall-details\">\ud83d\udd17<\/a><\/h3><p>Another class of problem is one I&rsquo;ve encountered before, but I don&rsquo;t remember exactly what to do about it. This doesn&rsquo;t mean that I&rsquo;m starting from a fresh search though. I do my best to keep some notes, or a pointer in my brain, so I can have a very targeted search and quickly resolve my issue.<\/p><p>I don&rsquo;t have the characters memorized that tell a date how to format itself into a specific string. But, I do know that the <a href=\"https:\/\/ruby-doc.org\/stdlib-3.1.0\/libdoc\/date\/rdoc\/DateTime.html#method-i-strftime\">ruby strftime documentation<\/a> has them exhaustively defined. When I need to format a date in a specific way, I don&rsquo;t remember how to do that. But I do remember where to go to find that out.<\/p><h3 id=\"revisit-assumptions\">Revisit Assumptions <a href=\"#revisit-assumptions\">\ud83d\udd17<\/a><\/h3><p>I also find myself searching when I already <em>know<\/em> a way to solve my problem. That may sound wasteful, but it&rsquo;s been beneficial for me. I take a time-boxed approach to learn how other people solve familiar problems. It gives me new insight. It may introduce me to a new tool. I could learn a new design pattern. I may discover something I like about my approach that I didn&rsquo;t even have an appreciation for before.<\/p><p>We work in an industry that&rsquo;s ever-changing. There are new tools, products, and mindsets coming and going. We can learn from all these. I may still use the approach I was going to when I started. But I&rsquo;m doing so not <strong>only<\/strong> because it&rsquo;s how I know how to do it. It&rsquo;s because I&rsquo;ve identified a set of alternatives. I&rsquo;ve developed a series of criteria to evaluate them on, and made an intentional choice on how to proceed.<\/p><h3 id=\"share-communal-knowledge\">Share Communal Knowledge <a href=\"#share-communal-knowledge\">\ud83d\udd17<\/a><\/h3><p>I&rsquo;ll also search for things I already know about to provide that information to others. I don&rsquo;t want people to only take my word for it. I want to provide some social proof, documentation, or other primary source. Now this person has that available to them for bookmarking or their notes. It can serve its purpose beyond the scope of a conversation with me.<\/p><p>It&rsquo;s very unlikely that I learned about what we&rsquo;re talking about by figuring it out on my own. I likely read, watched, or heard about it somewhere else. That original source deserves the credit. Sharing that with others allows them to gain the same context I have and recall it in the future.<\/p><h2 id=\"search-experience\">Search Experience <a href=\"#search-experience\">\ud83d\udd17<\/a><\/h2><p>When I&rsquo;m solving a direct problem or recalling details, I search when I feel that I cannot resolve my issue alone. I&rsquo;ve determined it&rsquo;ll be a quicker and more effective use of my time to seek outside perspectives.<\/p><p>There are many times where I search for items that I do know the answer to. I investigate new ways to solve the problem. Through that I gain more experience and make informed choices on the approach I take. I search to bolster my presentation of knowledge I have with information from others. Revising this information is beneficial to me, and by sharing it, I hope others benefit as well.<\/p><p>The next time you&rsquo;re searching for something, consider <em>why<\/em> you&rsquo;re searching for it. Use that answer to inform the approach you take. And I hope that you find what you&rsquo;re looking for.<\/p>"},{"title":"Scraping Buy: Scripting for a Purchase","link":"https:\/\/kevinjmurphy.com\/posts\/scraping-buy\/","pubDate":"Wed, 11 May 2022 16:53:59 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/scraping-buy\/","description":"<h2 id=\"an-opening-tune\">An Opening Tune <a href=\"#an-opening-tune\">\ud83d\udd17<\/a><\/h2><p>The piece of code I&rsquo;ve written in the last two months most interesting to me isn&rsquo;t anything I&rsquo;ve done for work. It isn&rsquo;t anything I wrote to <a href=\"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/\">find<\/a> a new job. It doesn&rsquo;t have tests. It doesn&rsquo;t even have a class. It&rsquo;s dirty, not up to my standards, and doesn&rsquo;t handle edge cases. But it does what I need it to.<\/p><h2 id=\"sad-song\">Sad Song <a href=\"#sad-song\">\ud83d\udd17<\/a><\/h2><p>I don&rsquo;t (and likely will never) consider myself a guitarist, but I like the guitar. I&rsquo;d like to have a Les Paul to go along with my Stratocaster, but I also like having money for things like food and shelter. I can&rsquo;t justify it in the budget, and that&rsquo;s fine. But maybe I could find something close enough for less money.<\/p><p>I zeroed in on a copy, and I decided on a Firefly. There was only one problem - I couldn&rsquo;t buy one. At least, not from them. Keeping them in stock was problematic. Not only because of supply chain issues, but because of their business model. They batch up a small number of guitars a few times a year for sale. I wasn&rsquo;t the only person with the same plan to buy one. They sell out quickly most of the time.<\/p><p>You can find them on the <a href=\"https:\/\/reverb.com\/marketplace?query=Firefly%20FFLPS\">secondhand market<\/a>. Typically, they&rsquo;re priced higher than their new price direct from the manufacturer. The demand is there. They don&rsquo;t make them often enough. And if you want one, you&rsquo;ve got to be fast.<\/p><h2 id=\"composing-a-number\">Composing A Number <a href=\"#composing-a-number\">\ud83d\udd17<\/a><\/h2><p>This is about the furthest thing from an important use of my time. Still, I became a bit&hellip;overtaken by the prospect of ordering one. I&rsquo;d refresh the site many times a day, just to check in. Once, I happened to refresh the site when they were uploading their fresh inventory. I was in! But, I did not get to the model I wanted in time. It sold out before I could find it.<\/p><p>Finally, I remembered that computers can do repetitive work for you, and that I knew how to request them to do so. I resolved to stop refreshing their product listing page. At least, I&rsquo;d stop refreshing it myself. I wrote a small script to do it for me.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#39;nokogiri&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#39;open-uri&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>doc <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Nokogiri<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">HTML<\/span>(<span style=\"color:#66d9ef\">URI<\/span><span style=\"color:#f92672\">.<\/span>open(<span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>root_url<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">\/collections\/fflp-electric-guitars&#34;<\/span>))<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>guitars <span style=\"color:#f92672\">=<\/span> doc<span style=\"color:#f92672\">.<\/span>search(<span style=\"color:#e6db74\">&#34;a&#34;<\/span>)<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> map { <span style=\"color:#f92672\">|<\/span>a<span style=\"color:#f92672\">|<\/span> a<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:href<\/span><span style=\"color:#f92672\">]<\/span> }<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> compact<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> select { <span style=\"color:#f92672\">|<\/span>a<span style=\"color:#f92672\">|<\/span> a<span style=\"color:#f92672\">.<\/span>include?(<span style=\"color:#e6db74\">&#34;\/collections\/fflp-electric-guitars\/products&#34;<\/span>) }<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> uniq<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> map{ <span style=\"color:#f92672\">|<\/span>a<span style=\"color:#f92672\">|<\/span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>root_url<span style=\"color:#e6db74\">}#{<\/span>a<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span> }<span style=\"color:#f92672\">.<\/span><\/span><\/span><span style=\"display:flex;\"><span> sort<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>puts guitars<\/span><\/span><\/code><\/pre><\/div><p>I wouldn&rsquo;t put this in production at work. It doesn&rsquo;t have consistent formatting! It&rsquo;s not clear exactly what this is doing! It&rsquo;s not tested. Literally. I don&rsquo;t mean it doesn&rsquo;t have automated tests. I mean I had no idea if this would work. When I wrote it, I couldn&rsquo;t test it against a page that had any products. I found one of their other products that did have inventory in stock, and built it against that. I hoped that the page for this product line would work the same, and that I didn&rsquo;t have a typo.<\/p><p>This doesn&rsquo;t keep track of their full inventory. It has no awareness of if there are pages of results. But, it&rsquo;s good enough to tell me that there <em>are<\/em> guitars for sale. That&rsquo;s a start. Hopefully. I tired of seeing the &ldquo;sold out&rdquo; page. And I wouldn&rsquo;t even know if it would work until that &ldquo;sold out&rdquo; page went away.<\/p><h2 id=\"automated-playback\">Automated Playback <a href=\"#automated-playback\">\ud83d\udd17<\/a><\/h2><p>Having the script is great, but if I have to run it myself, I might as well refresh the page in my browser. Once again, I needed to remind myself what I do for a living, and how I can ask computers to do things for me. Of course, that only happened when I came across <a href=\"https:\/\/simonwillison.net\/2020\/Oct\/9\/git-scraping\/\">this post<\/a>. In it, the author uses GitHub Actions to automate diff checks of websites. Now sure, their example was for trivial things like forest fires in California. But I could take the same approach for important things like guitars being in stock.<\/p><p>I modified my script to dump its output to a file, set up the GitHub Action, and subscribed to the repository. When the action pushed a commit because the page changed, I would get an email with a link to the commit. I could use that to then see the list of guitars for sale.<\/p><p>In theory. I still needed to see it happen. Ever.<\/p><h2 id=\"outro\">Outro <a href=\"#outro\">\ud83d\udd17<\/a><\/h2><p>It finally happened. And it just arrived.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/firefly_goldtop.jpg\" alt=\"A Firefly FFLPS Goldtop\"><\/figure><p>Code doesn&rsquo;t have to be perfect. It doesn&rsquo;t always need to adhere to your normal standards. Sometimes, it can be good enough to solve a problem you have - especially if it&rsquo;s temporary. Even better if you <em>know<\/em> it&rsquo;ll be temporary. I&rsquo;ve written lots of &ldquo;temporary&rdquo; code that I&rsquo;m sure is still in production, years later. Writing code like this is a gamble.<\/p><p>But the stakes were incredibly low here. I wouldn&rsquo;t put this in my portfolio if I were looking for a job. It served its purpose though. I&rsquo;m happy to retire this code. Thanks to it, I have other things to play with rather than maintain this.<\/p>"},{"title":"Code and the Coding Coders who Code it Podcast Appearance","link":"https:\/\/kevinjmurphy.com\/posts\/coding-coders-podcast-2022\/","pubDate":"Wed, 11 May 2022 08:27:36 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/coding-coders-podcast-2022\/","description":"<p>I am the latest guest on the Code and the Coding Coders who Code it <a href=\"https:\/\/www.buzzsprout.com\/1927628\">podcast<\/a>.<\/p><p>Drew Bragg and I cover prepping a talk for RailsConf, finding a job, and writingwords about code that aren&rsquo;t code. You can listen <a href=\"https:\/\/www.buzzsprout.com\/1927628\/10596923\">here<\/a>.<\/p><p>Drew and I stuck around unexpectedly to discuss our shared love of hockey, thatyou can hear <a href=\"https:\/\/www.buzzsprout.com\/1927628\/10597043-bonus-hockey-episode\">here<\/a>.<\/p>"},{"title":"Joining BookBub","link":"https:\/\/kevinjmurphy.com\/posts\/bookbub-start\/","pubDate":"Mon, 02 May 2022 06:23:22 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/bookbub-start\/","description":"<p>Today is my first day joining the team at <a href=\"https:\/\/www.bookbub.com\/welcome\">BookBub<\/a>. I&rsquo;m looking forward to meeting and working with the team there.<\/p><p>To everyone who I heard from and met with during my <a href=\"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/\">job search<\/a>, thank you. I had so many great conversations and was overwhelmed with the volume and quality of responses I received.<\/p>"},{"title":"Browser History Confessional: Searching My Recent Searches","link":"https:\/\/kevinjmurphy.com\/posts\/browser-history\/","pubDate":"Wed, 13 Apr 2022 08:55:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/browser-history\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>We all only have so much working memory available in our brains. Developers may joke about spending their day composing search engine queries. The reason it&rsquo;s a joke is because of the truth behind it. Search-driven development is a reality.<\/p><p>Join me, and my actual search history, on a journey to solve recent challenges I faced. I&rsquo;ll categorize the different types of information I often search for. You&rsquo;ll leave with tips on retrieving the knowledge you need for your next bug, feature, or pull request.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/R7LkHjJdH9o\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/youtu.be\/R7LkHjJdH9o\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/browser-history-confessional-searching-my-recent-searches\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/browser-history\">Code Examples<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/searching-for-a-reason\/\">Blog Post<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/browser-history-confessional-proposal\/\">Proposal<\/a><\/li><\/ul><h2 id=\"search-driven-development\">Search-Driven Development <a href=\"#search-driven-development\">\ud83d\udd17<\/a><\/h2><p>I find it&rsquo;s important to consider <em>why<\/em> I&rsquo;m searching for something. It helps guide the path I&rsquo;ll take to resolve my query and helps frame the problem in a familiar context. In my own work, I&rsquo;ve found there are four main categories of reasons why I search. They all relate to how much experience I have with the problem I&rsquo;m working on.<\/p><h3 id=\"solve-direct-problems\">Solve Direct Problems <a href=\"#solve-direct-problems\">\ud83d\udd17<\/a><\/h3><p>When I&rsquo;m trying to do something for the first time, I may lack any context and perspective to have a starting point. I may get stuck mid-way through. And, being a novice at this task, I need to seek outside perspectives. I&rsquo;ll often build that up by searching for how others have accomplished the same task.<\/p><p>With a specific error that I&rsquo;m looking to fix, there&rsquo;s one thing I almost always do first. I <strong>copy and paste the error<\/strong> into a search engine. This is a high-leverage, low-effort move. It&rsquo;s unlikely you&rsquo;re the only person to have encountered this issue before. It requires validating that what you find are people experiencing the same problem. You must also determine for yourself if their solution works for you. However, it is still a go-to first step for me.<\/p><p>That may not be a great approach if the error is fairly generic. It may not yield results if a lot of the error message is about code specific to your application. In that case, I review the error message, and consider the context where it arived. Search the README of the framework, dependency, or tool you&rsquo;re working with. Visit open and closed issues. Search their documentation. Find their forums. Use log messages to add to your search criteria.<\/p><p>You may conduct a thorough search using all these avenues and still not have an answer. You might start feeling frustrated, or angry. You might be doubting your abilities. When that creeps in, the most important thing I can recommend is that you stop.<\/p><p><strong>Stop.<\/strong><\/p><p>Move away and do something else for a few minutes, even if it&rsquo;s get a new glass of water. Give your brain some time to wander. Ponder what you&rsquo;re experiencing in a different physical location. Do something else to take your mind off of it.<\/p><p>When you&rsquo;re ready to regroup, re-review what the problem is and what steps you&rsquo;ve taken to address it. Look at your search history to see what you&rsquo;ve already looked for. Try to find any gaps in what you&rsquo;ve already explored.<\/p><p>At this point, remember that you don&rsquo;t have to solve this alone. You don&rsquo;t need to use the computer by yourself to solve the problem. If the computer could fix itself, we wouldn&rsquo;t have a job as developers! Reach out to your friends and your coworkers for help. Their fresh perspective may bring a quick resolution. They might suggestion other things to search for. They may help you with a different strategy to take.<\/p><h3 id=\"recall-details\">Recall Details <a href=\"#recall-details\">\ud83d\udd17<\/a><\/h3><p>Another class of problem is one I&rsquo;ve encountered before, but I don&rsquo;t remember exactly what to do about it. This doesn&rsquo;t mean that I&rsquo;m starting from a fresh search though. I do my best to keep some notes, or a pointer in my brain, so I can have a very targeted search and quickly resolve my issue.<\/p><p>I don&rsquo;t have the characters memorized that tell a date how to format itself into a specific string. But, I do know that the <a href=\"https:\/\/ruby-doc.org\/stdlib-3.1.0\/libdoc\/date\/rdoc\/DateTime.html#method-i-strftime\">ruby strftime documentation<\/a> has them exhaustively defined. When I need to format a date in a specific way, I don&rsquo;t remember how to do that. But I do remember where to go to find that out.<\/p><h3 id=\"revisit-assumptions\">Revisit Assumptions <a href=\"#revisit-assumptions\">\ud83d\udd17<\/a><\/h3><p>I also find myself searching when I already <em>know<\/em> a way to solve my problem. That may sound wasteful, but it&rsquo;s been beneficial for me. I take a time-boxed approach to learn how other people solve familiar problems. It gives me new insight. It may introduce me to a new tool. I could learn a new design pattern. I may discover something I like about my approach that I didn&rsquo;t even have an appreciation for before.<\/p><p>We work in an industry that&rsquo;s ever-changing. There are new tools, products, and mindsets coming and going. We can learn from all these. I may still use the approach I was going to when I started. But I&rsquo;m doing so not <strong>only<\/strong> because it&rsquo;s how I know how to do it. It&rsquo;s because I&rsquo;ve identified a set of alternatives. I&rsquo;ve developed a series of criteria to evaluate them on, and made an intentional choice on how to proceed.<\/p><h3 id=\"share-communal-knowledge\">Share Communal Knowledge <a href=\"#share-communal-knowledge\">\ud83d\udd17<\/a><\/h3><p>I&rsquo;ll also search for things I already know about to provide that information to others. I don&rsquo;t want people to only take my word for it. I want to provide some social proof, documentation, or other primary source. Now this person has that available to them for bookmarking or their notes. It can serve its purpose beyond the scope of a conversation with me.<\/p><p>It&rsquo;s very unlikely that I learned about what we&rsquo;re talking about by figuring it out on my own. I likely read, watched, or heard about it somewhere else. That original source deserves the credit. Sharing that with others allows them to gain the same context I have and recall it in the future.<\/p><h2 id=\"search-experience\">Search Experience <a href=\"#search-experience\">\ud83d\udd17<\/a><\/h2><p>When I&rsquo;m solving a direct problem or recalling details, I search when I feel that I cannot resolve my issue alone. I&rsquo;ve determined it&rsquo;ll be a quicker and more effective use of my time to seek outside perspectives.<\/p><p>There are many times where I search for items that I do know the answer to. I investigate new ways to solve the problem. Through that I gain more experience and make informed choices on the approach I take. I search to bolster my presentation of knowledge I have with information from others. Revising this information is beneficial to me, and by sharing it, I hope others benefit as well.<\/p><p>The next time you&rsquo;re searching for something, consider <em>why<\/em> you&rsquo;re searching for it. Use that answer to inform the approach you take. And I hope that you find what you&rsquo;re looking for.<\/p>"},{"title":"Available For Hire","link":"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/","pubDate":"Mon, 28 Mar 2022 12:16:47 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/available-for-hire-2022\/","description":"<p>UPDATE: I am <a href=\"https:\/\/kevinjmurphy.com\/posts\/bookbub-start\/\">no longer<\/a> looking for work. Below is my original post from when I was.<\/p><p>I\u2019m a Ruby and Rails developer looking for my next full-time remote role. Here\u2019s a little <a href=\"https:\/\/kevinjmurphy.com\/about\/\">about me<\/a> and what I\u2019m looking for. If this sounds like a good fit for your organization, I\u2019d love to talk with you.<\/p><h2 id=\"how-i-work\">How I Work <a href=\"#how-i-work\">\ud83d\udd17<\/a><\/h2><p>I \u2764\ufe0f fast feedback loops for development, code review, and customer reaction. I don\u2019t TDD all the time, but I don\u2019t travel far without checking in with my tests to check in on me. I focus on back-end improvements and code organization. I like to orchestrate small, understandable components to ship your next feature.<\/p><p>I get energized from setting my team up for success. That may mean evaluating alternative implementations. It could be determining prioritization of planned work. It will mean working with teammates to level up technical abilities. I\u2019m proud to see my peers thrive.<\/p><p>I\u2019ll admit, I\u2019m not quick - I\u2019m thorough. I confirm requirements, identify edge cases, and document intentional choices. I deliver code focused on maintainability and understanding. I build for the long haul, keeping in mind today\u2019s constraints.<\/p><h2 id=\"what-i-work-on\">What I Work On <a href=\"#what-i-work-on\">\ud83d\udd17<\/a><\/h2><p>Ruby is home for me, and where I\u2019d like to focus. In the last year I\u2019ve also deployed Elixir and Golang code to production. I have a <a href=\"https:\/\/www.linkedin.com\/in\/kevinmurphydev\/\">track record<\/a> of delivering solutions to big problems with small teams.<\/p><p>I have experience in healthcare tech, insurance tech, and consulting. I carry that history with me. It informs decisions and choices we make today to do right by customers and stakeholders.<\/p><p>I\u2019ve <a href=\"https:\/\/kevinjmurphy.com\/speaking\/\">spoken<\/a> at RailsConf and RubyConf multiple times. I\u2019ve presented at many meetups. I prepare presentations for internal and external audiences for us all to learn.<\/p><p>This last year I&rsquo;ve <a href=\"https:\/\/kevinjmurphy.com\/featured-writing\/\">written articles<\/a> featured in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a>, <a href=\"https:\/\/ruby.libhunt.com\/\">Awesome Ruby<\/a>, and <a href=\"https:\/\/rubyradar.dev\/\">Ruby Radar<\/a>. My next post could be highlighting the great work we do and key learnings we discover together.<\/p><h2 id=\"what-im-looking-for\">What I&rsquo;m Looking For <a href=\"#what-im-looking-for\">\ud83d\udd17<\/a><\/h2><p>I\u2019m looking for a full-time remote Staff\/Principal\/Architect role. I want to mentor team members, ship meaningful work, and learn from each other. If that sounds like a fit for your team, let\u2019s talk.<\/p>"},{"title":"Finding an Initially Confusing Result in Rails","link":"https:\/\/kevinjmurphy.com\/posts\/find-or-initialize-by-block\/","pubDate":"Mon, 14 Mar 2022 07:30:41 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/find-or-initialize-by-block\/","description":"<h2 id=\"initial-impressions\">Initial Impressions <a href=\"#initial-impressions\">\ud83d\udd17<\/a><\/h2><p>We run into an old friend Bob and a new friend Carol on the street. Bob recently got married and changed their last name. We&rsquo;re meeting Carol for the first time. We update our mental Rolodex of friends during this meeting.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Smith&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Carol&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>count<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Chance encounter on the street<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>bob <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>friend<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> friend<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jones&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>carol <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Carol&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>friend<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> friend<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Thompson&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>What are the values of Carol&rsquo;s and Bob&rsquo;s last names in our working memory right now during this encounter?<\/p><h3 id=\"carols-last-name\">Carol&rsquo;s Last Name <a href=\"#carols-last-name\">\ud83d\udd17<\/a><\/h3><p>Carol&rsquo;s last name is Thompson. We have no other friends named Carol, so we create a new object and in the block, set their last name to Thompson.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>carol<span style=\"color:#f92672\">.<\/span>last_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Thompson&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><h3 id=\"bobs-last-name\">Bob&rsquo;s Last Name <a href=\"#bobs-last-name\">\ud83d\udd17<\/a><\/h3><p>Bob&rsquo;s last name is <em>still<\/em> &ldquo;Smith&rdquo;, the value it&rsquo;s persisted in our database as.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>bob<span style=\"color:#f92672\">.<\/span>last_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Smith&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Even though in our block, we set their last name to Jones, it didn&rsquo;t take affect. It seems we forgot our friend&rsquo;s new last name! This could lead to an embarrassing situation later on in the conversation. What happened?<\/p><h2 id=\"finding-the-source-of-the-confusion\">Finding The Source of the Confusion <a href=\"#finding-the-source-of-the-confusion\">\ud83d\udd17<\/a><\/h2><p>Thanks to Rails&rsquo; <a href=\"https:\/\/api.rubyonrails.org\/v7.0.2\/classes\/ActiveRecord\/Relation.html#method-i-find_or_initialize_by\">documentation<\/a>, we discover the <code>find_or_initialize_by<\/code> method is in <code>ActiveRecord::Relation<\/code>. From there, we can look at the <a href=\"https:\/\/github.com\/rails\/rails\/blob\/de53ba56cab69fb9707785a397a59ac4aaee9d6f\/activerecord\/lib\/active_record\/relation.rb#L226\">source code<\/a> of the method:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">find_or_initialize_by<\/span>(attributes, <span style=\"color:#f92672\">&amp;<\/span>block)<\/span><\/span><span style=\"display:flex;\"><span> find_by(attributes) <span style=\"color:#f92672\">||<\/span> <span style=\"color:#66d9ef\">new<\/span>(attributes, <span style=\"color:#f92672\">&amp;<\/span>block)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"attributing-the-difference\">Attributing The Difference <a href=\"#attributing-the-difference\">\ud83d\udd17<\/a><\/h2><p>If we find an existing record by the attributes provided, then we return that record. If not, <code>find_by<\/code> returns <code>nil<\/code> and we visit the right-hand side of the expression. That will create a new record with the attributes and <a href=\"https:\/\/kevinjmurphy.com\/posts\/activerecord-new-block\/\">pass the block<\/a> to <code>new<\/code>. Notice that the block is not executed at all when <code>find_by<\/code> returns a record.<\/p><h2 id=\"mistakenly-blocking-out-our-friends-new-name\">Mistakenly Blocking Out Our Friend&rsquo;s New Name <a href=\"#mistakenly-blocking-out-our-friends-new-name\">\ud83d\udd17<\/a><\/h2><p>That explains why Bob&rsquo;s last name isn&rsquo;t updated in our memory. We documented the change, but it never took effect. <code>find_or_initialize_by<\/code> didn&rsquo;t use the block. The saved representation for Bob returned from the method without executing the block.<\/p><p>When using <code>find_or_initialize_by<\/code> with a block, pay careful attention. The block will only execute in one of those conditions - when initializing an object.<\/p><h2 id=\"clarifying-our-initial-intent\">Clarifying Our Initial Intent <a href=\"#clarifying-our-initial-intent\">\ud83d\udd17<\/a><\/h2><p>With this information, let&rsquo;s consider when and how we want to evaluate the code in the block.<\/p><h3 id=\"update-neither-found-nor-new-records\">Update Neither Found Nor New Records <a href=\"#update-neither-found-nor-new-records\">\ud83d\udd17<\/a><\/h3><p>Passing a block to <code>find_or_initialize_by<\/code> is <em>optional<\/em>. When the criteria we&rsquo;re using to find a record is all we want our new record to have, there&rsquo;s no need to supply the block.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>bob <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Our goal right now is to change Bob&rsquo;s last name. This alone does not get us there.<\/p><h3 id=\"only-update-new-records\">Only Update New Records <a href=\"#only-update-new-records\">\ud83d\udd17<\/a><\/h3><p>This is what our initial implementation is doing. As a reminder, we have:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>bob <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>friend<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> friend<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jones&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We\u2019re searching for our friend named \u201cBob\u201d. When we find one, we get our friend back from the database. When we don&rsquo;t, we instantiate a new friend and also set their last name as \u201cJones\u201d.<\/p><p>That may be confusing to yourself and others reading this code in the future. It may not be clear when the block executes. As an alternative, we could choose to explicitly call that out. A friend that\u2019s not persisted will respond to <code>new_record?<\/code> with <code>true<\/code>, and we can use that to only update their last name when they\u2019re new.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>bob <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">if<\/span> bob<span style=\"color:#f92672\">.<\/span>new_record?<\/span><\/span><span style=\"display:flex;\"><span> bob<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jones&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The block allows us to interact with new records beyond setting the attributes to identify the record. Block or not though, this isn\u2019t the end result we want <strong>here<\/strong> in this example.<\/p><h2 id=\"update-both-found-and-new-records\">Update Both Found and New Records <a href=\"#update-both-found-and-new-records\">\ud83d\udd17<\/a><\/h2><p>This is the functionality we desire in this particular case. We don&rsquo;t want to find a friend named &ldquo;Bob Jones&rdquo;. We won&rsquo;t find one. We know Bob&rsquo;s last name changed. But if we do have a friend Bob stored in our database, we want to update their last name.<\/p><p>We can use <code>find_or_initialize_by<\/code> so that we have a friend instance, whether we have one stored or not. From there, we can use what we&rsquo;ve learned in the prior sections. We&rsquo;ll avoid passing the method a block - and we&rsquo;ll unconditionally change the returned value to set the last name.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>bob <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Friend<\/span><span style=\"color:#f92672\">.<\/span>find_or_initialize_by(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Bob&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>bob<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jones&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>With this change, we make sure to commit our friend\u2019s new last name to memory. Whether they existed in our system before or not, their last name is now &ldquo;Jones&rdquo;.<\/p><h2 id=\"finding-the-initial-inspiration\">Finding The Initial Inspiration <a href=\"#finding-the-initial-inspiration\">\ud83d\udd17<\/a><\/h2><p>Thanks to Ben Drozdoff for the conversation that led to this post.<\/p><p>Thanks to Matthew Draper for the suggestion to augment this with a solutions-based conclusion, depending on when you expect the code in the block to execute.<\/p>"},{"title":"Business Process Management: A Developer's Business","link":"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/","pubDate":"Mon, 28 Feb 2022 07:19:33 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/","description":"<h2 id=\"a-developers-business\">A Developer&rsquo;s Business <a href=\"#a-developers-business\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/\">Situational Leadership<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">Competitive Advantage<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/core-competency\/\">Core Competency<\/a><\/li><li><strong>Business Process Management<\/strong><\/li><\/ol><p>Knowing a thing or two about the mindset of someone on the &ldquo;business&rdquo; side of your company can be very valuable. That doesn&rsquo;t mean you need to spend two years of your life getting a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/\">MBA<\/a> to learn about this (but don&rsquo;t let me stop you).<\/p><p>This post won&rsquo;t be about accounting, marketing, entrepreneurship, or making quarterly revenue projections. Instead, it will focus on a concept I learned in business school that helps me in my day-to-day as a developer.<\/p><h2 id=\"business-process\">Business Process <a href=\"#business-process\">\ud83d\udd17<\/a><\/h2><p>Companies run by following repeated steps to produce a given output. Some companies are more explicit at defining and recognizing these activities than others. If you look hard enough - you&rsquo;ll find them.<\/p><p>Dr. Thomas Davenport <a href=\"https:\/\/books.google.com\/books?id=kLlIOMGaKnsC&amp;lpg=PP1&amp;pg=PA5#v=onepage&amp;q&amp;f=false\">defines a process<\/a> as:<\/p><blockquote><p>a structured, measured set of activities designed to produce a specific output.<\/p><\/blockquote><p>As software developers, we translate these processes into systemic rules or features.<\/p><h2 id=\"business-process-management\">Business Process Management <a href=\"#business-process-management\">\ud83d\udd17<\/a><\/h2><p>People need to tend to these processes. The processes must serve the needs of the organization in the best possible way. The person completing the steps may be responsible for that. There may be a separate team to optimize the work that the operational team is conducting.<\/p><p>The Association of Business Process Management <a href=\"https:\/\/www.abpmp.org\/page\/BPM_Profession\">states<\/a> that Business Process Management<\/p><blockquote><p>enables an enterprise to align its business processes to its business strategy.<\/p><\/blockquote><p>Manage and deliver iterative improvements of a <a href=\"https:\/\/www.bpminstitute.org\/resources\/articles\/what-bpm-anyway-business-process-management-explained\">business process<\/a> with the following steps:<\/p><h3 id=\"design\">Design <a href=\"#design\">\ud83d\udd17<\/a><\/h3><p>The design step must start with documenting the existing process. Observe the current activities taken to achieve the goal. Write those steps down without alteration. Stopping here still has value to the organization. It may lead to greater standardization. It may be easier to hire and train new people. It can improve transparency, visibility, and awareness in the organization.<\/p><p>The design phase goes one step further. Propose an incremental improvement to the current state. This may be a new activity that wasn&rsquo;t done before. It may be removing steps of the process. It could be changing the number of people or who the people are that complete the work. The change must meet the process goals and the team&rsquo;s challenges with the current process.<\/p><h3 id=\"model\">Model <a href=\"#model\">\ud83d\udd17<\/a><\/h3><p>In this phase, test the designed future improvement before making the change. Seek to determine with greater confidence that it will improve the process. Conduct various &ldquo;what if&rdquo; analyses to attempt to validate that the change is a good fit before enacting it. For processes that resemble assembly line production, statistical software can predict quantitative results.<\/p><h3 id=\"execute\">Execute <a href=\"#execute\">\ud83d\udd17<\/a><\/h3><p>Now that you have some evidence that this change will be beneficial - do it! However, don&rsquo;t do more than is necessary to prove efficacy of the process change. This may mean implementing these changes as &ldquo;manual&rdquo; workarounds first. Invest the resources to automate the change or codify it in the system in a future iteration.<\/p><p>Take an approach that yields the signal to confirm the change is having the affect you want with minimal disruption to your business. In software development, this may look like an incremental roll-out. The process change starts with a pilot program. After that, incrementally introduce it to more and more of the population that interacts with the process.<\/p><h3 id=\"monitor\">Monitor <a href=\"#monitor\">\ud83d\udd17<\/a><\/h3><p>It&rsquo;s not enough to make the change. Now we need to verify that it&rsquo;s having the affect we want. Should we continue the incremental roll-out approach we&rsquo;re taking? Should we should roll the change back? To know, we must track and report on key performance indicators (KPIs) previously defined. We will observe the actual impact of the change to ensure that it is meeting the anticipated goals.<\/p><h3 id=\"optimize\">Optimize <a href=\"#optimize\">\ud83d\udd17<\/a><\/h3><p>Now that we&rsquo;ve introduced a change and verified it&rsquo;s benefiting the process, it&rsquo;s time to start all over again. Business Process Management is an exercise in continuous improvement. Identify the next bottleneck to address and design a proposed a solution to alleviate it.<\/p><h3 id=\"developing-business-processes\">Developing Business Processes <a href=\"#developing-business-processes\">\ud83d\udd17<\/a><\/h3><p>Much of our work as software developers involves managing processes. Some of us are constantly working to optimize our individual workflow. That magical keyboard shortcut, or the custom alias, that&rsquo;ll save you precious seconds of typing over the course of your entire career? That&rsquo;s improving your personal business processes.<\/p><p>Our feature delivery work focuses on business process management. We may be automating a &ldquo;manual&rdquo; process. We may be changing an existing flow in our application to make it better for users. We may be lovingly putting a spreadsheet on the Internet. In all those cases, we need to consider business process management steps. Using this framework will ensure the changes we&rsquo;re making are having the impact we desire.<\/p><p>When building computer systems that didn&rsquo;t exist before, we need to consider if the context change should affect the process. It may not make sense to recreate the existing process when using a computer to manage it. The team may need to <a href=\"https:\/\/hbr.org\/1990\/07\/reengineering-work-dont-automate-obliterate\">re-engineer<\/a> the process, re-imagining it from the ground-up.<\/p><p>Whether incrementally changing business processes, or re-engineering them, don&rsquo;t forget that we design systems and software for humans. Ensure that the ultimate focus is not on solving the optimization problem. Consider the impact and consequences of the work we do, both for users of the system and those impacted by the decisions made by the system.<\/p>"},{"title":"Core Competency: A Developer's Business","link":"https:\/\/kevinjmurphy.com\/posts\/core-competency\/","pubDate":"Mon, 14 Feb 2022 07:19:33 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/core-competency\/","description":"<h2 id=\"a-developers-business\">A Developer&rsquo;s Business <a href=\"#a-developers-business\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/\">Situational Leadership<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">Competitive Advantage<\/a><\/li><li><strong>Core Competency<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/\">Business Process Management<\/a><\/li><\/ol><p>Knowing a thing or two about the mindset of someone on the &ldquo;business&rdquo; side of your company can be very valuable. That doesn&rsquo;t mean you need to spend two years of your life getting a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/\">MBA<\/a> to learn about this (but don&rsquo;t let me stop you).<\/p><p>This post won&rsquo;t be about accounting, marketing, entrepreneurship, or making quarterly revenue projections. Instead, it will focus on a concept I learned in business school that helps me in my day-to-day as a developer.<\/p><h2 id=\"core-competency\">Core Competency <a href=\"#core-competency\">\ud83d\udd17<\/a><\/h2><p>A firm&rsquo;s <a href=\"https:\/\/link.springer.com\/chapter\/10.1007\/978-3-662-41482-8_46\">core competencies<\/a> are the specific qualities or characteristics driving its success. A core competency must meet the following criteria:<\/p><ul><li>Provides access to various markets.<\/li><li>Contributes to perceived customer benefit.<\/li><li>Difficult for competitors to imitate.<\/li><\/ul><p>This is <a href=\"https:\/\/www.techtarget.com\/searchcio\/definition\/core-competency#:~:text=what%20it%20can%20do%20better%20than%20any%20other\">often<\/a> referred <a href=\"https:\/\/www.investopedia.com\/terms\/c\/core_competencies.asp#:~:text=what%20it%20can%20do%20better%20than%20anyone%20else\">to<\/a> as what the organization can do better than any other group. After identifying a core competency, invest in it to achieve and keep that advantage. Focus more on improving core competencies and less on other areas of the business.<\/p><h2 id=\"developing-core-competencies\">Developing Core Competencies <a href=\"#developing-core-competencies\">\ud83d\udd17<\/a><\/h2><p>Knowing your company&rsquo;s core competencies helps to answer your next build or buy or borrow conundrum. Developers face this when deciding <em>how<\/em> to build a feature.<\/p><p>We&rsquo;ll consider upcoming features from our <a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/#competitive-advantage\">previously mentioned<\/a> blogging platform for medical professionals. Keeping an eye on our core competencies, let&rsquo;s decide how to deliver changes to our comment system.<\/p><h2 id=\"build\">Build <a href=\"#build\">\ud83d\udd17<\/a><\/h2><p>Our last article introduced a comment moderation system to automatically flag HIPAA violations. We delivered that knowing we&rsquo;re <a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/#focus\">focusing<\/a> on the medical market. That feature allows us to stand out in the marketplace. The innovative functionality can be a core competency of the firm. As such, we should be building all the work related to that feature in-house.<\/p><p>Our developers need a keen understanding of HIPAA violations, or how to identify them. Anything we can invest to build up that knowledge we should. It has immense value by making it available to customers through our product. Developers improving the functionality of HIPAA violation identification is of critical importance. The more time they can spend focusing on that, the better this unique capability will be.<\/p><h2 id=\"buy\">Buy <a href=\"#buy\">\ud83d\udd17<\/a><\/h2><p>We won&rsquo;t get far only having <strong>one<\/strong> feature in our comment system. To drive re-engagement, we want to send emails to authors when someone comments on a post. We need a way to <em>send<\/em> these emails to the authors. But, we want our developers focusing on HIPAA violations, not email delivery.<\/p><p>Email delivery is not a core competency of the company. We may not even want to know what a SMTP server is, never mind how to configure it. Luckily, there are other companies who <em>do<\/em> have that as a core competency. We can engage in commerce! In exchange for some of our money, we can enjoy their knowledge of and expertise in email delivery.<\/p><p>We&rsquo;re best served paying a vendor to handle email delivery for us. We&rsquo;ll access this capability from their API in our application. Their price is significantly less than what it would cost to build and maintain this functionality ourselves. Even if not, the opportunity cost of developers working on email delivery may be too great. That&rsquo;s time they&rsquo;re not improving the identification of HIPAA violations! We should buy this functionality. Our customers will receive emails, and our team can focus on our core competencies.<\/p><h2 id=\"borrow\">Borrow <a href=\"#borrow\">\ud83d\udd17<\/a><\/h2><p>Blog posts on our platform are likely to be the talk of the town. We&rsquo;ll generate a lot of conversation in the form of comments. Loading all those comments on initial page load will negatively affect page performance. Pages that are slow to load will be slow to generate further conversation and engagement. To mitigate the performance issue, we&rsquo;re going to paginate the comments. A subset of comments will load at first, and readers will be able to request more comments by pressing a button.<\/p><p>Comment pagination is not a core competency of our organization. Even though it&rsquo;s something we need to do, we&rsquo;re not in the business of paginating comments. As such, like we learned from the last section, we should look to buy this capability. It turns out, there aren&rsquo;t a lot of other companies that are in the business of paginating comments. Even if we wanted to give someone money to do this for us, there don&rsquo;t seem to be many takers. Fortunately, as developers, we have another option.<\/p><p>We can use the kind work of open source developers who maintain pagination libraries. That will allow us to add this capability to our application with minimal work on our end. From a developer&rsquo;s perspective, more considerations must go into <a href=\"https:\/\/www.mikeperham.com\/2016\/02\/09\/kill-your-dependencies\/\">adding a dependency<\/a>. The same is true whether buying capabilities or pulling in a library. Strictly considering our core competency, adding the pagination library is a justifiable choice. Any opportunity we have to add capabilities while focusing on HIPAA violations is a win.<\/p><h2 id=\"core-lesson\">Core Lesson <a href=\"#core-lesson\">\ud83d\udd17<\/a><\/h2><p>A company&rsquo;s core competencies help it stand out in the marketplace. The same is true for you. What unique talents and skills do you bring to bear? How can you use them so that a company will reward you with a new job, title, or raise? Think about what your awesome qualities are and how you can leverage them.<\/p><p>For the final lesson in our series, we&rsquo;ll discuss <a href=\"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/\">business process management<\/a>.<\/p>"},{"title":"Competitive Advantage: A Developer's Business","link":"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/","pubDate":"Mon, 31 Jan 2022 07:19:33 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/","description":"<h2 id=\"a-developers-business\">A Developer&rsquo;s Business <a href=\"#a-developers-business\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/\">Situational Leadership<\/a><\/li><li><strong>Competitive Advantage<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/core-competency\/\">Core Competency<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/\">Business Process Management<\/a><\/li><\/ol><p>Knowing a thing or two about the mindset of someone on the &ldquo;business&rdquo; side of your company can be very valuable. That doesn&rsquo;t mean you need to spend two years of your life getting a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/\">MBA<\/a> to learn about this (but don&rsquo;t let me stop you).<\/p><p>This post won&rsquo;t be about accounting, marketing, entrepreneurship, or making quarterly revenue projections. Instead, it will focus on a concept I learned in business school that helps me in my day-to-day as a developer.<\/p><h2 id=\"competition\">Competition <a href=\"#competition\">\ud83d\udd17<\/a><\/h2><p>The work you do and the company you work for is bound to have other people or firms that do the same thing. Likely not in the same way, and likely not exact replicas - but they can serve the same purpose. You would need to exist in a very narrow niche of the market for that not to be true. Life isn&rsquo;t, or doesn&rsquo;t have to be, the adversarial match that the business world makes it out to be. But, the truth is that most of the time people and companies need to stand out in some way for a paycheck or customers.<\/p><h2 id=\"competitive-advantage\">Competitive Advantage <a href=\"#competitive-advantage\">\ud83d\udd17<\/a><\/h2><p>A firm&rsquo;s <a href=\"https:\/\/corporatefinanceinstitute.com\/resources\/knowledge\/strategy\/competitive-advantage\/\">competitive advantage<\/a> allows it to flourish against competitors. There are three general competitive <a href=\"https:\/\/www.ifm.eng.cam.ac.uk\/research\/dstools\/porters-generic-competitive-strategies\/\">strategies<\/a>:<\/p><ul><li>Cost.<\/li><li>Differentiation.<\/li><li>Focus.<\/li><\/ul><p>To explore these different strategies, let&rsquo;s imagine we&rsquo;re going into business together. You and I are going to build the next great blogging platform. We&rsquo;ll make it a success by using each of these competitive advantage strategies.<\/p><h3 id=\"cost\">Cost <a href=\"#cost\">\ud83d\udd17<\/a><\/h3><p>Cost competitive advantage is about being the cheapest option; cheaper than other competitors. A cost strategy is easy to replicate. Once you find out what your competitors charge, you charge less than that. Congratulations, you have utilized the cost competitive advantage strategy in the market! That said, this strategy is difficult to maintain long-term success. Unfortunately, to stay in business, you can&rsquo;t only sell the most things. You also need to make a profit on those sales.<\/p><p>Staying in business using the cost strategy is the more difficult part of the equation. To keep it up, complete the work in less time or with fewer resources than others need. Proprietary technology or exclusive access to special materials can also make this sustainable. Some operational efficiency compared to the rest of the market must exist.<\/p><p>Using a cost strategy alone to gain a foothold in the blogging market would be difficult. You can blog for <em>free<\/em> on a lot of platforms today. We <em>could<\/em> <strong>pay<\/strong> people to use our service, rather than <strong>charge<\/strong> them. Maintaining that over the long term would be particularly difficult while remaining solvent.<\/p><h3 id=\"differentiation\">Differentiation <a href=\"#differentiation\">\ud83d\udd17<\/a><\/h3><p>Winning in the blogging space on cost will be a difficult choice. Instead, let&rsquo;s try meeting the needs of bloggers in ways competitors don&rsquo;t. This may mean having a higher quality offering that&rsquo;s valuable to users. We may introduce innovative products or services that our competitors don&rsquo;t have. We may be able to turn around the output of using our service faster than others. Our product may not be better in any way; but we&rsquo;ll lean heavily into marketing. When people hear &ldquo;blogging&rdquo;, they&rsquo;ll think of us. Each of these choices would be using a differentiation strategy to attract customers.<\/p><p>Our blogging platform could offer a concierge service for new customers. We migrate their existing content to our platform by hand, ourselves. We could offer to ghost-write their first five blogs. Their personal concierge can sit with them to provide a tour of all the features of our software. Other companies may not provide that level of service. Some customers may be willing to pay for that extra attention. Our differentiated offering will make us an attractive choice for that customer.<\/p><h3 id=\"focus\">Focus <a href=\"#focus\">\ud83d\udd17<\/a><\/h3><p>With a focus strategy, we&rsquo;re going to find a tailored niche to dedicate our efforts towards. We&rsquo;ll find a narrowly-targeted market segment and design to their needs. Our features and services will cater to that audience exclusively.<\/p><p>We won&rsquo;t try to be the best blogging platform for all bloggers. Instead, we&rsquo;ll target customers writing about medical care. That&rsquo;s still a large audience. Academic researchers, hospitals, drug companies, insurers, private practice doctors, nursing staff, and more would be potential customers. We may choose to focus even more narrowly on one of those personas.<\/p><p>With that focus, we&rsquo;ll build a comment moderation system for their needs. It&rsquo;ll automatically flag potential HIPAA violations. That&rsquo;s a feature sports bloggers aren&rsquo;t too interested in. It&rsquo;s unlikely we&rsquo;ll entice them to become a customer with that. Yet, many people in the healthcare space may pay for help with HIPAA compliance.<\/p><p>That doesn&rsquo;t mean that the sports blogger <strong>can&rsquo;t<\/strong> use our platform. We won&rsquo;t build features purely to meet their needs though. You won&rsquo;t find a plugin to scroll the scores of last night&rsquo;s games on our platform. Our features and future development effort will meet the needs of medical bloggers.<\/p><h2 id=\"developing-competitive-advantage\">Developing Competitive Advantage <a href=\"#developing-competitive-advantage\">\ud83d\udd17<\/a><\/h2><p>Now that we know what competitive advantage <em>is<\/em>, why should you care as a developer? You may not want to run your own company and need to stand out against competitors. You many not see yourself in a position to make strategic decisions for a company or a department. Even so, you <em>will<\/em> work with people that <strong>do<\/strong> have that responsibility. Part of your job is to help them gain the company&rsquo;s desired competitive advantage.<\/p><p>Do you know what your company&rsquo;s stated, or intended, advantage is? That will help you propose new initiatives, knowing it&rsquo;s aligned with company strategy. It can inform how your team builds its next feature. Given our focus, we can frame our design reviews to make sure features add value for medical bloggers.<\/p><p>You should also consider your personal competitive advantage strategy compared to other developers. That may sound obvious if you&rsquo;re looking for a new job. It&rsquo;s also important when asking for a promotion, or solidifying your performance review. The labor market now (early 2022) has a limited supply of developers, compared to job openings. Even so, the number of developers looking or interested in jobs is likely more than only <strong>you<\/strong>. What&rsquo;s your strategy to set yourself apart?<\/p><p>Competitive advantage feeds in nicely to our next topic: <a href=\"https:\/\/kevinjmurphy.com\/posts\/core-competency\/\">core competency<\/a>.<\/p>"},{"title":"Situational Leadership: A Developer's Business","link":"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/","pubDate":"Mon, 17 Jan 2022 07:25:00 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/","description":"<h2 id=\"a-developers-business\">A Developer&rsquo;s Business <a href=\"#a-developers-business\">\ud83d\udd17<\/a><\/h2><ol><li><strong>Situational Leadership<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">Competitive Advantage<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/core-competency\/\">Core Competency<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/\">Business Process Management<\/a><\/li><\/ol><p>Knowing a thing or two about the mindset of someone on the &ldquo;business&rdquo; side of your company can be very valuable. That doesn&rsquo;t mean you need to spend two years of your life getting a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/\">MBA<\/a> to learn about this (but don&rsquo;t let me stop you).<\/p><p>This post won&rsquo;t be about accounting, marketing, entrepreneurship, or making quarterly revenue projections. Instead, it will focus on a concept I learned in business school that helps me in my day-to-day as a developer.<\/p><h2 id=\"leadership\">Leadership <a href=\"#leadership\">\ud83d\udd17<\/a><\/h2><p>I know, you&rsquo;re a developer. You want to write code, not lead people. You&rsquo;re nota manager and maybe you&rsquo;re not in a &ldquo;leadership&rdquo; position as an individualcontributor either. However, for the betterment of yourself and your team, ithelps to study leadership.<\/p><p>Presumably you have people leading you. It can help to knowhow they think about different situations, or what training they might have attheir disposal. You may not do the leading, but wouldn&rsquo;t it be great if youcould help your leadership by letting them know what they could do to bestsupport you?<\/p><p>Even without the &ldquo;right&rdquo; title, everyone in a team still has opportunities tolead. It can mean ownership over a piece of functionality. It can be assistingnew members of your team acclimate to your codebase or processes. It can comein when mentoring other people on your team, formally or not. Situations topractice leadership skills are likely frequently presenting themselves to you.You may be doing it without thinking about it or knowing it!<\/p><p>One model of leadership that I find valuable to keep in mind is SituationalLeadership. Let&rsquo;s learn what it is and talk about how we can use it in our jobsas developers.<\/p><h2 id=\"situational-leadership\">Situational Leadership <a href=\"#situational-leadership\">\ud83d\udd17<\/a><\/h2><p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Situational_leadership_theory\">Situational Leadership Model<\/a> posits that there are four different styles ofleadership.<\/p><h3 id=\"directing\">Directing <a href=\"#directing\">\ud83d\udd17<\/a><\/h3><p>In the directing style, the leader provides close, detailed supervision withclear instructions and expectations. The team&rsquo;s role in this style is inexecuting the task. This style is effective when someone is unfamiliar with thework, or needs to be retrained on an activity. Crises and emergencies are otherplaces where the directing style may be necessary.<\/p><h3 id=\"coaching\">Coaching <a href=\"#coaching\">\ud83d\udd17<\/a><\/h3><p>A leader using the coaching style still has ownership over the decision-makingprocess, much like in the directing style. However, more of their time should bespent getting buy-in from the team on why decisions are made the way they are.Employ this approach when the team is comfortable with the individual mechanicsof performing the tasks, can provide input on the process, and can be motivatedby understanding the goal or end-state that performing the work should deliver.<\/p><h3 id=\"supporting\">Supporting <a href=\"#supporting\">\ud83d\udd17<\/a><\/h3><p>With the supporting style, ownership on decision-making is shifted from theleader and moved to the team. The leader&rsquo;s role is to facilitate discussionwithin the team to reach decisions, to introduce them to the people and toolsthat will aid in their success, to be a sounding board as they make decisions,and to encourage them to take action on their own.<\/p><h3 id=\"delegating\">Delegating <a href=\"#delegating\">\ud83d\udd17<\/a><\/h3><p>A leader using the delegating style provides and trusts the team with autonomyon day-to-day decision-making. The leader&rsquo;s energy is focused on the overallvision and strategy for the organization they lead. They provide the team withthe tools to execute those high-level concepts and translate them into tacticaldecisions and tasks.<\/p><h2 id=\"leading-situation-by-situation\">Leading Situation By Situation <a href=\"#leading-situation-by-situation\">\ud83d\udd17<\/a><\/h2><p>A leader must be able to exhibit all of these styles. Otherwise, they&rsquo;ll only beeffective in very specific scenarios with a very specific group of people at agiven time. The authors of this framework acknowledge that by <a href=\"https:\/\/blogs.umass.edu\/shabazz\/2014\/08\/\">saying<\/a> thatSituational Leadership is about<\/p><blockquote><p>Choosing the right leadership style for the right people.<\/p><\/blockquote><p>In order to determine which style to use at a given time, consider how muchrelationship and task behavior is required by the individual or team at themoment.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/situational-leadership.png\"><\/figure><p>The more assistance a person needs in understanding the mechanisms neededto complete the work, the higher the amount of task behavior is needed. Themore the person needs to understand the background, or business value, of anitem of work, or help navigating the political or social pressures involved incompleting the work, the higher amount of relationship behavior the leader needsto provide.<\/p><h2 id=\"sprint-planning-with-situational-leadership\">Sprint Planning With Situational Leadership <a href=\"#sprint-planning-with-situational-leadership\">\ud83d\udd17<\/a><\/h2><p>One scenario where Situational Leadership can be helpful to us and our team asdevelopers is in Sprint Planning. When reviewing the units of work that we&rsquo;repreparing to work on, we can identify what level of support and leadershippeople in the team will need to succeed in completing the tickets.<\/p><p>If you have a task where people have no familiarity with what is being asked ofthem and they&rsquo;ll be working on it, then the <em>directing<\/em> style may beappropriate. Make sure that the ticket has clearly defined instructions andacceptance criteria. Also, consider adding a second member of the team to theticket so they can pair and have someone else who will be directly accountablefor helping see the work through to completion.<\/p><p>The <em>coaching<\/em> style may be used in supporting a teammate on a task where theyhave some understanding of the mechanics needed to complete the ticket. I findthis can be most effectively done when you can draw a parallel or analogy tosimilar work. Link to a branch, or a pull request, or a commit that&rsquo;s similar,and explain how this ticket will be the same and different from what&rsquo;s donethere. The person is still provided clear direction on what to do, but is givenmore room to complete it in their own way, with more information about <strong>why<\/strong>something is done and how it&rsquo;s similar to other work.<\/p><p>Use the <em>supporting<\/em> style on a task that will be familiar to the person doingit. You may perhaps organize a quick chat with stakeholders to make sureeveryone is aligned and let this person ask any questions they might have. Checkin with the person individually to see what they need, find out if they want tobounce any ideas off of you, or if they have suggestions for what others can doto help them complete their work.<\/p><p>Is there a rote task or something done time after time that a team member isworking on? <em>Delegate<\/em> the work to them entirely and allow them the space andrespect to get in there and execute on their own. However, if you are the persontaking on this rote task, or you identify that happening with someone else,consider speaking up to add someone else to the ticket as well so they can learnfrom your experience and get more familiar with the actions needed to completeit. It&rsquo;s now your responsibility to help bring everyone else on the team up tospeed in your area of expertise, for the benefit of us all.<\/p><h2 id=\"leading-to-a-conclusion\">Leading To A Conclusion <a href=\"#leading-to-a-conclusion\">\ud83d\udd17<\/a><\/h2><p>Exercising leadership skills is something we can all find opportunities topractice, regardless of our expertise, title, or role in our teams. SituationalLeadership is a framework you can use to consider what concrete actions you canmodel in a given scenario to provide the most effective leadership for yourselfand your team.<\/p><p>Now that we&rsquo;ve covered leadership, let&rsquo;s explore your company&rsquo;s <a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">competitive advantage<\/a>.<\/p>"},{"title":"2021 Year-End Review","link":"https:\/\/kevinjmurphy.com\/posts\/2021-review\/","pubDate":"Mon, 13 Dec 2021 17:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/2021-review\/","description":"<p>This isn&rsquo;t a &ldquo;real&rdquo; post. This is a summary of all the things that made up myyear in 2021. Thanks to all that were a part of it.<\/p><h2 id=\"writing\">Writing <a href=\"#writing\">\ud83d\udd17<\/a><\/h2><p>I published 15 articles about Ruby or software development in general this year. I started the year sharing how to cache <a href=\"https:\/\/kevinjmurphy.com\/posts\/caching-all-native-gem-platforms\/\">native Ruby gems<\/a>. That post ended up in Nokogiri&rsquo;s <a href=\"https:\/\/nokogiri.org\/tutorials\/installing_nokogiri.html#using-vendorcache-to-deploy-to-another-architecture\">docs<\/a>, which was an <a href=\"https:\/\/kevinjmurphy.com\/posts\/nokogiri-notoriety\/\">exciting surprise<\/a>.<\/p><p>I ended the year writing about what&rsquo;s special with <a href=\"https:\/\/kevinjmurphy.com\/posts\/ruby-setter-return-value\/\">setter methods<\/a> in Ruby. In between, I&rsquo;ve written about <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/\">coverage<\/a>, <a href=\"https:\/\/kevinjmurphy.com\/posts\/remote-ruby-143\/\">best practices<\/a>, <a href=\"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/\">evaluating alternatives<\/a>, and a handful of <a href=\"https:\/\/kevinjmurphy.com\/tags\/ruby\/\">Ruby<\/a> and <a href=\"https:\/\/kevinjmurphy.com\/tags\/rails\/\">Rails<\/a> topics.<\/p><p>I drastically increased my public writing in 2021, and it&rsquo;s coincided with thefirst year I&rsquo;m aware of people sharing my work. I already mentioned being inNokogiri&rsquo;s docs. I&rsquo;ve also had my writing featured in <a href=\"https:\/\/rubyweekly.com\/\">Ruby Weekly<\/a> <strong>ten<\/strong> times,<a href=\"https:\/\/rubyradar.dev\/\">Ruby Radar<\/a> <strong>six<\/strong> times, and <a href=\"https:\/\/ruby.libhunt.com\/\">Awesome Ruby<\/a> once in 2021.<\/p><h2 id=\"speaking\">Speaking <a href=\"#speaking\">\ud83d\udd17<\/a><\/h2><p>As of this post&rsquo;s publication, I&rsquo;m still not comfortable meeting in person(hopefully that changes in time for RailsConf 2022), but I enjoy speaking, so Idid my best to take part in virtual conferences I was aware of. I submitted sixproposals to three conferences (yes, you can submit multiple proposals toconferences!) that had a virtual component, and was accepted to one.<\/p><p>At RailsConf 2021, I shared the parts of a <a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/\">MBA curriculum<\/a> that I find valuableto me as a software developer.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/6ldCGo29w5g\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><p>One benefit of virtual events meant that I was able to speak with differentregional groups without needing to travel. This year, I presented to groups in:<\/p><ul><li><a href=\"https:\/\/www.meetup.com\/Phillyrb\">Philadelphia<\/a> (twice!)<\/li><li><a href=\"https:\/\/www.meetup.com\/ChicagoRuby\">Chicago<\/a><\/li><li><a href=\"https:\/\/www.meetup.com\/bostonrb\">Boston<\/a><\/li><li><a href=\"https:\/\/www.meetup.com\/columbusrb\">Columbus<\/a><\/li><li><a href=\"https:\/\/www.meetup.com\/nashrb\">Nashville<\/a><\/li><li><a href=\"https:\/\/lrug.org\/\">London<\/a><\/li><\/ul><p>Hopefully in future years I&rsquo;ll be able to get to these areas in person, but fornow, I&rsquo;m thankful to the organizers for allowing me to participate in theirvirtual events.<\/p><p>Lastly, I was invited as a podcast guest for the first time in 2021. I joinedRails with Jason (now Code with Jason) to talk neither Rails nor code; we<a href=\"https:\/\/www.codewithjason.com\/podcast\/9478219-096-engineering-mba-with-kevin-murphy\/\">discussed<\/a>the skills I learned in a MBA program that are helpful to me as a softwaredeveloper. Later in the year, I was on Remote Ruby to <a href=\"https:\/\/remoteruby.transistor.fm\/143\">talk<\/a>about best practices, and when they might not be the best.<\/p><h2 id=\"reading\">Reading <a href=\"#reading\">\ud83d\udd17<\/a><\/h2><p>For the first time, I was a technical reviewer for a book. Twice, in fact! I wasprivileged to get an early peek and provide feedback on Noel Rappin&rsquo;s two books.<\/p><ul><li><a href=\"https:\/\/pragprog.com\/titles\/nrclient\/modern-front-end-development-for-rails\/\">Modern Front-End Development for Rails<\/a><\/li><li><a href=\"https:\/\/pragprog.com\/titles\/tailwind\/modern-css-with-tailwind\/\">Modern CSS with Tailwind<\/a><\/li><\/ul><p>It was an unexpected, and weird, experience to receive final copies in the mailand see that my words (in the form of a blurb) are the first you read in eachbook. Don&rsquo;t let that deter you though - each book gets better after that.<\/p><h2 id=\"employer\">Employer <a href=\"#employer\">\ud83d\udd17<\/a><\/h2><p>After five years with <a href=\"https:\/\/www.thegnar.com\/\">The Gnar Company<\/a>, I started aposition at <a href=\"https:\/\/www.vendr.com\/\">Vendr<\/a> towards the end of this year. I&rsquo;mthankful to all my teammates at both companies for being great collaborators.<\/p><h2 id=\"personal\">Personal <a href=\"#personal\">\ud83d\udd17<\/a><\/h2><p>2021 was a hard year personally, for reasons that were shared by most this year,and for other reasons that aren&rsquo;t unique to me but just happened to occur thisyear. That&rsquo;s all I have to say about that here, but let&rsquo;s talk aboutthings that helped this year.<\/p><h3 id=\"new-to-me-in-2021\">New (to me) in 2021 <a href=\"#new-to-me-in-2021\">\ud83d\udd17<\/a><\/h3><p>While a healthy dose of known comforts and familiar music played no small partin my year, here are the things I encountered for the first time that helped getme through 2021.<\/p><h4 id=\"covid-19-vaccine\">COVID 19 Vaccine <a href=\"#covid-19-vaccine\">\ud83d\udd17<\/a><\/h4><p>If you are in an area where you&rsquo;re eligible and able to get a COVID vaccine, Iencourage you to do so. For yourself. For me. For all of us. In the US, you canfind a place to do that <a href=\"https:\/\/www.vaccines.gov\/\">here<\/a>.<\/p><h4 id=\"taskmaster\">Taskmaster <a href=\"#taskmaster\">\ud83d\udd17<\/a><\/h4><p>I&rsquo;m a year late on this compared to everyone else in the US who discovered thison <a href=\"https:\/\/www.youtube.com\/c\/Taskmaster\">YouTube<\/a>, but maybe it&rsquo;s better thatI had it saved for 2021. It&rsquo;s a British panel game show where contestantscompete for the best completion of various &ldquo;tasks&rdquo;, making fun of themselves andeach other in the process.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/7b9psaoeL-M\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h4 id=\"if-i-cant-have-love-i-want-power\">If I Can\u2019t Have Love, I Want Power <a href=\"#if-i-cant-have-love-i-want-power\">\ud83d\udd17<\/a><\/h4><p>I know, I&rsquo;m entirely predictable. Anything Trent Reznor is involved with I&rsquo;mobligated to devour. That said, Halsey&rsquo;s latest album is so powerful, layered,and enjoyable, that I&rsquo;m sure if I were a Spotify user, it would be my mostplayed album of the year.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/6E5s-KEKO3Q\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h4 id=\"broken-in-the-best-possible-way\">Broken (in the best possible way) <a href=\"#broken-in-the-best-possible-way\">\ud83d\udd17<\/a><\/h4><p>I&rsquo;m no stranger to Jenny Lawson&rsquo;s writing, and her latest from this year isanother of my favorites. Her bravery, irreverence, and humor are perfectlymatched with my sensibilities. Plus, you can order it online from her very ownphysical <a href=\"https:\/\/www.nowherebookshop.com\/book\/9781250077035\">bookstore<\/a> andget a signed copy.<\/p><h4 id=\"john-5\">John 5 <a href=\"#john-5\">\ud83d\udd17<\/a><\/h4><p>This isn&rsquo;t necessarily new (though he does have a new album that came out <a href=\"https:\/\/john-5.com\/main\/discography\/sinner-2021\/\">thisyear<\/a>), but I dug into hisentire catalogue this year. He&rsquo;s a session guitarist clearly capable of playing<a href=\"https:\/\/youtu.be\/ayB-ir-NHb4\">any guitar in any style<\/a>, but whose personal workmainly focuses on metal shredding (with a touch of bluegrass), all on aTelecaster.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/uxBwmY5NW-g\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"looking-forward\">Looking Forward <a href=\"#looking-forward\">\ud83d\udd17<\/a><\/h2><p>What am I hoping for in 2022?<\/p><h3 id=\"further-engaging-with-the-ruby-and-developer-community\">Further engaging with the Ruby and developer community <a href=\"#further-engaging-with-the-ruby-and-developer-community\">\ud83d\udd17<\/a><\/h3><p>If you have a podcast that you think I might be a good guest on, if you run aregional meetup looking for speakers, if you have a conference that I could be afit for, if you run a publication that features content I might write, if youhave an affinity group I can help out with, or if you&rsquo;d just like to meet,please reach out.<\/p><h3 id=\"covid-vaccines-available-to-children-under-5\">COVID vaccines available to children under 5 <a href=\"#covid-vaccines-available-to-children-under-5\">\ud83d\udd17<\/a><\/h3><p>You can&rsquo;t rush science, but I look forward to having a fully-vaccinated house.<\/p><h3 id=\"bruce-springsteens-no-nukes-concert\">Bruce Springsteen&rsquo;s No Nukes Concert <a href=\"#bruce-springsteens-no-nukes-concert\">\ud83d\udd17<\/a><\/h3><p>This won&rsquo;t be new in 2022 - it&rsquo;s from 1979 - and it&rsquo;s available <strong>now<\/strong>, but Ihave it on my Christmas list, so it may not be until 2022 that I can take inthis bit of history from the E Street Band.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/LzsAPhGpMkA\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"thank-you\">Thank You <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>If you made it this far, I have to believe this isn&rsquo;t your first introduction tome and you&rsquo;re a repeat reader. Thank you for doing that this year, and I hopeyou continue to track my work in the future. It&rsquo;s been a great joy to see peopleinteracting with things I produced this year, so if you&rsquo;ve liked anything Iput out this year, please, let me know - and thanks!<\/p>"},{"title":"Setter Method Return Values In Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-setter-return-value\/","pubDate":"Wed, 17 Nov 2021 19:04:22 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-setter-return-value\/","description":"<h2 id=\"can-i-take-your-order\">Can I Take Your Order? <a href=\"#can-i-take-your-order\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re opening a brand new diner, and each customer can have their current orderset, which will add to their list of orders.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Customer<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:orders<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @orders <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_order<\/span><\/span><\/span><span style=\"display:flex;\"><span> @orders<span style=\"color:#f92672\">.<\/span>last<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_order<\/span><span style=\"color:#f92672\">=<\/span>(new_order)<\/span><\/span><span style=\"display:flex;\"><span> @orders <span style=\"color:#f92672\">&lt;&lt;<\/span> new_order<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;thank you for being our valued customer&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Because customer service is our top priority, we provide a message wheneverthey place an order reminding them how lucky we feel to be serving themtheir meal.<\/p><h2 id=\"negative-reviews\">Negative Reviews <a href=\"#negative-reviews\">\ud83d\udd17<\/a><\/h2><p>After our soft opening, we&rsquo;re excited to read everyone&rsquo;s favorable commentsabout their food. However, we&rsquo;re getting slammed with badreviews about our curt interactions with customers when they place an order. Wewent out of our way to modify the setter&rsquo;s return value to give a specialmessage; what&rsquo;s wrong?<\/p><h2 id=\"mystery-shopper\">Mystery Shopper <a href=\"#mystery-shopper\">\ud83d\udd17<\/a><\/h2><p>In order to get to the bottom of this, we put on our fake mustache and glassesto go undercover at our own restaurant. What we find <strong>shocks us<\/strong>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> definitely_not_the_owner <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Customer<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> definitely_not_the_owner<span style=\"color:#f92672\">.<\/span>current_order <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;pancakes&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;pancakes&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>That&rsquo;s not the welcoming experience we explicitly developed! We shouldn&rsquo;t bebarking back their order in a matter of fact way; we should be thanking them.Something surprising is happening here; now we need to find what it is.<\/p><h2 id=\"training-manual\">Training Manual <a href=\"#training-manual\">\ud83d\udd17<\/a><\/h2><p>Our method is explicit about what the return value should be.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_order<\/span><span style=\"color:#f92672\">=<\/span>(new_order)<\/span><\/span><span style=\"display:flex;\"><span> @orders <span style=\"color:#f92672\">&lt;&lt;<\/span> new_order<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;thank you for being our valued customer&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We expect the last thing evaluated in the method to be what&rsquo;s returned to thecaller. And that&rsquo;s the case&hellip;most of the time. After consulting Ruby&rsquo;s<a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/doc\/syntax\/methods_rdoc.html#label-Return+Values\">documentation<\/a>, we find an exception:<\/p><blockquote><p>Note that for assignment methods the return value will be ignored when using the assignment syntax. Instead, the argument will be returned<\/p><\/blockquote><p>This is our exact case! Our setter method to set the current order is returningthe argument passed to it, and not the result of the last line. It&rsquo;s beingcompletely ignored in favor of the argument.<\/p><h2 id=\"demanding-exceptional-service\">Demanding Exceptional Service <a href=\"#demanding-exceptional-service\">\ud83d\udd17<\/a><\/h2><p>The documentation does provide a way in which customers can see our nice messagethough:<\/p><blockquote><p>The actual return value will be returned when invoking the method directly<\/p><\/blockquote><p>So, while we cannot enforce it, if callers know the right way to ask, we&rsquo;llthank them.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> customer <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Customer<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> customer<span style=\"color:#f92672\">.<\/span>current_order <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;pancakes&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;pancakes&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>irb(main):<span style=\"color:#ae81ff\">003<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> customer<span style=\"color:#f92672\">.<\/span>public_send(<span style=\"color:#e6db74\">:current_order<\/span><span style=\"color:#f92672\">=<\/span>, <span style=\"color:#e6db74\">&#34;french toast&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;thank you for being our valued customer&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"setting-the-table-for-next-time\">Setting The Table For Next Time <a href=\"#setting-the-table-for-next-time\">\ud83d\udd17<\/a><\/h2><p>Ruby setter, or assignment, methods have a special understanding andexpectation for what they&rsquo;ll return. This helps enforce a consistent API, butcan lead to surprising results when writing your own setter method.<\/p><p>Setter methods will return the argument passed to it, regardless of what thelast evaluated statement in the method is. Callers can receive the value of thelast statement if they call the method in a particular way; however, it would bevery unconventional to expect someone to do that.<\/p><p>In the case of our restaurant, that meant a pivoting in our branding. We startedpositioning ourselves as an outfit focused on intense efficiency and limitedcustomer interaction - with a secret message any customer in the know can passus to force a little more welcoming of a response with any new order.<\/p>"},{"title":"Joining Vendr","link":"https:\/\/kevinjmurphy.com\/posts\/vendr-start\/","pubDate":"Mon, 25 Oct 2021 07:00:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/vendr-start\/","description":"<p>I&rsquo;m happy to say that today is my first day as a member of <a href=\"https:\/\/www.vendr.com\/\">Vendr<\/a>! I&rsquo;m looking forward to meeting everyone. I&rsquo;m excited to help the development team help everyone reduce the friction involved with buying SaaS products.<\/p>"},{"title":"Ruby's Got You Covered","link":"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/","pubDate":"Thu, 21 Oct 2021 20:00:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/","description":"<h2 id=\"coverage\">Coverage <a href=\"#coverage\">\ud83d\udd17<\/a><\/h2><p>Perhaps you&rsquo;ve heard of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Code_coverage\">test coverage<\/a>,which is a measurement of how much of your application code is executed whenyour tests run. That number is typically represented as a percentage, and peoplemay use that metric to assess the relative health of a codebase. The efficacy ofsuch metrics is <a href=\"https:\/\/kevinjmurphy.com\/posts\/remote-ruby-143\/\">debated<\/a>, but the metrics arestill prevalent.<\/p><p>This article will demonstrate the mechanism ruby provides to measure coverageand present some examples for how to use it and is a summary of the informationI shared about coverage at <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a>.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/EyLO0EEm3BQ\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"running-coverage\">Running Coverage <a href=\"#running-coverage\">\ud83d\udd17<\/a><\/h2><p>Ruby ships with a <a href=\"https:\/\/docs.ruby-lang.org\/en\/3.0.0\/Coverage.html\">Coverage module<\/a> as part of the language.To use it, you must first <code>require<\/code> the module.<\/p><p>After doing that, you have access to <code>Coverage<\/code>. Coverage begins running whenyou call the <code>start<\/code> method. It then expects the file you want coverage to beassessed on to be <code>require<\/code>d or <code>load<\/code>ed. Finally, you can see coverage&rsquo;s outputby calling <code>result<\/code>.<\/p><p>For example, let&rsquo;s say we&rsquo;re all in a band and we&rsquo;re practicing a new<a href=\"https:\/\/youtu.be\/_7Qk9MdyiOM\">cover song<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># rehearsal.rb<\/span><\/span><\/span><span style=\"display:flex;\"><span>our_band <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Band<\/span><span style=\"color:#f92672\">.<\/span>new(name: <span style=\"color:#e6db74\">&#34;Blogger Band&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>song <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">CoverMe<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>song<span style=\"color:#f92672\">.<\/span>original_artist <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Bruce Springsteen&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>song<span style=\"color:#f92672\">.<\/span>band <span style=\"color:#f92672\">=<\/span> our_band<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>song<span style=\"color:#f92672\">.<\/span>play<\/span><\/span><\/code><\/pre><\/div><p>In order to run coverage on this file, we can do the following:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start<\/span><\/span><span style=\"display:flex;\"><span>load <span style=\"color:#e6db74\">&#34;rehearsal.rb&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><\/code><\/pre><\/div><h2 id=\"coverage-modes\">Coverage Modes <a href=\"#coverage-modes\">\ud83d\udd17<\/a><\/h2><p>Ruby&rsquo;s coverage module has many modes, or different ways of assessing coverage.Each mode answers a different question about the code that was run under coverage:<\/p><ul><li>Lines - how many times was each line executed?<\/li><li>Oneshot Lines - which lines were executed?<\/li><li>Methods - how many times was each method executed?<\/li><li>Branches - how many times was each conditional executed?<\/li><\/ul><p>You can specify which modes to run by passing an argument to <code>Coverage.start<\/code>.<\/p><h3 id=\"lines-coverage\">Lines Coverage <a href=\"#lines-coverage\">\ud83d\udd17<\/a><\/h3><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">lines<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>This is the mode that runs if you do not pass any arguments to <code>Coverage.start<\/code>.Each relevant line has a counter that is incremented each time the line isvisited in code execution while coverage is running. Irrelevant lines, thosethat are things like empty lines or <code>end<\/code> statements, are ignored. At theconclusion, you will see how many times each line is executed.<\/p><p>Our guitarist wants to track how often they break a string during rehearsal. Astring is broken when the <code>@broken<\/code> instance variable is set.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">String<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">break_string<\/span><\/span><\/span><span style=\"display:flex;\"><span> @broken <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">BrokenStringSound<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Coverage&rsquo;s result provides a hash, where the keys are all the files that wererun while coverage was running. Each value is a hash that has a key for themode(s) of coverage run.<\/p><p>For lines coverage, the value of that inner hash is an array showing how manytimes each line was executed. The integer at index 0 of this array shows howmany times line 1 was run.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>{<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;string.rb&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> {<span style=\"color:#e6db74\">:lines<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#66d9ef\">nil<\/span>,<span style=\"color:#f92672\">...]<\/span>},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>The <code>nil<\/code> represents an irrelevant line, in this case, an <code>end<\/code> statement. Toanswer our question, we need to see how many times line 3 of the string file wasrun, which is index 2 in the array - and we see our guitarist broke 4 strings inone rehearsal.<\/p><h3 id=\"oneshot-lines-coverage\">Oneshot Lines Coverage <a href=\"#oneshot-lines-coverage\">\ud83d\udd17<\/a><\/h3><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">oneshot_lines<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Similar to lines coverage, this also documents that a relevant line was executed while coverage was running. However, it\u2019s a binary report of whether it was executed or not. It will not tell you how often. This may be sufficient in many cases, and comes with the benefit of being more performant every subsequent time a particular line of code is executed under coverage.<\/p><p>The drummer has a break in the song where they play a small fill.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Drum<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">small_fill<\/span><\/span><\/span><span style=\"display:flex;\"><span> bang_tom<\/span><\/span><span style=\"display:flex;\"><span> roll_snare(<span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">2<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> extend_fill?<\/span><\/span><span style=\"display:flex;\"><span> hit_crash_cymbal<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> strike_ride_cymbal<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The band isn&rsquo;t sure if the drummer is hitting the crash cymbal during the fill.To find out, they can use oneshot lines coverage, which will tell if the line ofcode is executed. They don&rsquo;t care how many times; only if it ever happened.<\/p><p>The result looks similar to lines coverage:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>{<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;drum.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#e6db74\">:oneshot_lines<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">7<\/span>, <span style=\"color:#ae81ff\">3<\/span>, <span style=\"color:#f92672\">...]<\/span>},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>The values in the array are different from lines coverage though. Here, eachinteger in the array is a line number that was executed. Remember, oneshot lineswon&rsquo;t tell you how many times a line was run. The order of elements does notmatter, unlike lines coverage.<\/p><p>In the case of our drum fill, 7 is in the array, which is the line number to hitthe crash cymbal, so the drummer is extending the fill.<\/p><h3 id=\"methods-coverage\">Methods Coverage <a href=\"#methods-coverage\">\ud83d\udd17<\/a><\/h3><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(methods: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Methods coverage brings the granularity of lines coverage up to a coarser grain. Rather than tracking individual lines, it\u2019s concerned with whether a particular method is executed. It can be a 10 line method where the first line is the only line ever executed. Methods coverage will still consider that as executed the same as a 20 line method where each line is executed.<\/p><p>Now that our guitarist knows they break a lot of strings, they need to thin outthe gear they bring to gigs so they have more room in their bag for strings.They&rsquo;re wondering which effects pedals they&rsquo;re even using on their pedal board.They have a lot, and each of them responds to <code>trigger<\/code>, which turns them offor on when you press them.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">ReverbPedal<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">OverdrivePedal<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">DelayPedal<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can use methods coverage to see which of those pedals are being triggeredduring rehearsal.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>{<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;reverb_pedal.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">:methods<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#66d9ef\">ReverbPedal<\/span>, <span style=\"color:#e6db74\">:trigger<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">2<\/span>}},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;overdrive_pedal.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">:methods<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#66d9ef\">OverdrivePedal<\/span>, <span style=\"color:#e6db74\">:trigger<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">0<\/span>}},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;delay_pedal.rb&#34;<\/span><span style=\"color:#f92672\">=&gt;<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">:methods<\/span><span style=\"color:#f92672\">=&gt;<\/span>{<span style=\"color:#f92672\">[<\/span><span style=\"color:#66d9ef\">DelayPedal<\/span>, <span style=\"color:#e6db74\">:trigger<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">]=&gt;<\/span><span style=\"color:#ae81ff\">3<\/span>}},<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>Unlike the results we&rsquo;ve seen thus far, this isn&rsquo;t only returning an array inthe value of the mode hash. Instead, there&rsquo;s another hash where the keyidentifies the method, and the value is the number of times the method isexecuted. Let&rsquo;s dig into what each of the elements identifying a method are.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#66d9ef\">OverdrivePedal<\/span>, <span style=\"color:#e6db74\">:trigger<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">4<\/span>, <span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># ^ ^ ^ ^ ^ ^<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Class | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Name | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Method | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Name | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Start | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Line | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Start | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Column| |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># End |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Line |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># End<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Column<\/span><\/span><\/span><\/code><\/pre><\/div><p>To help our guitarist clean up their pedal board, we can see that the overdrivepedal isn&rsquo;t used at all, and can be left at home next time.<\/p><h3 id=\"branches-coverage\">Branches Coverage <a href=\"#branches-coverage\">\ud83d\udd17<\/a><\/h3><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">branches<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Branches Coverage tracks execution of different conditional paths and documents how often those different paths are run. The unique benefit that this provides over lines coverage is in conditionals that execute multiple code paths in a single line, such as ternary statements. You may have a part of that conditional that\u2019s never run or tested, but you would not know that if you\u2019re relying on lines coverage alone.<\/p><p>Our singer wants to use an echo effect during the song, and has a friend settingthe intensity as they practice.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">CoverMe<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">chorus<\/span>(number)<\/span><\/span><span style=\"display:flex;\"><span> echo_intensity <span style=\"color:#f92672\">=<\/span> number<span style=\"color:#f92672\">.<\/span>positive? <span style=\"color:#f92672\">&amp;&amp;<\/span> number<span style=\"color:#f92672\">.<\/span>even? ? <span style=\"color:#ae81ff\">10<\/span> : <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Lyric<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">line<\/span>: line, <span style=\"color:#e6db74\">effect<\/span>: <span style=\"color:#e6db74\">:echo<\/span>, <span style=\"color:#e6db74\">effect_level<\/span>: echo_intensity)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>During one run-through of the song, they&rsquo;re happy with the effect and want tocheck how often they used each intensity. Because this is expressed as aternary, we <strong>can&rsquo;t<\/strong> use lines coverage. We could use it if the method werestructured like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">chorus<\/span>(number)<\/span><\/span><span style=\"display:flex;\"><span> echo_intensity <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">if<\/span> number<span style=\"color:#f92672\">.<\/span>positive? <span style=\"color:#f92672\">&amp;&amp;<\/span> number<span style=\"color:#f92672\">.<\/span>even?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">10<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">else<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">30<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, in either case, we <em>can<\/em> use branches coverage to see which of thedifferent branches were followed.<\/p><p>The output of branches coverage looks similar to that of methods coverage.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>{<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;cover_me.rb&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:branches<\/span> <span style=\"color:#f92672\">=&gt;<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:if<\/span>, <span style=\"color:#ae81ff\">0<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">25<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">67<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=&gt;<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:then<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">60<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">62<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:else<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">65<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">67<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>The differences from methods coverage are:<\/p><ol><li>Branches coverage nests each branch within its conditional, so the datastructure is nested one level deeper than methods coverage.<\/li><li>Branches coverage assigns a unique identifier to each conditional or branch.<\/li><\/ol><p>Let&rsquo;s look at what each of the elements identifying a branch are.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:then<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">60<\/span>, <span style=\"color:#ae81ff\">34<\/span>, <span style=\"color:#ae81ff\">62<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># ^ ^ ^ ^ ^ ^<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Branch | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Id | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Start | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Line | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Start | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Column | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># | |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># End |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Line |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># |<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># End<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># Column<\/span><\/span><\/span><\/code><\/pre><\/div><p>Looking at the results, the satisfactory performance had the echo intensitycranked up the 30 the entire time. The <code>else<\/code> condition of the ternary was theonly branch executed. Now the band knows how to set the effect for their nextperformance.<\/p><h3 id=\"all-coverage-modes\">All Coverage Modes <a href=\"#all-coverage-modes\">\ud83d\udd17<\/a><\/h3><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">:all<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>Passing the <code>:all<\/code> symbol to <code>Coverage.start<\/code> will ask it to run every coveragemode; however, if you inspect the output, you&rsquo;ll notice that one is missing.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>require <span style=\"color:#e6db74\">&#34;coverage&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>start(<span style=\"color:#e6db74\">:all<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>load <span style=\"color:#e6db74\">&#34;rehearsal.rb&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>result <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Coverage<\/span><span style=\"color:#f92672\">.<\/span>result<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>result<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;guitar.rb&#34;<\/span><span style=\"color:#f92672\">].<\/span>keys<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:lines<\/span>, <span style=\"color:#e6db74\">:methods<\/span>, <span style=\"color:#e6db74\">:branches<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Oneshot lines is missing!<\/p><p>Oneshot lines and lines modes <a href=\"https:\/\/github.com\/ruby\/ruby\/blob\/d92f09a5eea009fa28cd046e9d0eb698e3d94c5c\/ext\/coverage\/coverage.c#L53\">cannot be run at the same time<\/a>,so lines coverageis run, as you can still use it to answer if a line was executed at all.<\/p><h2 id=\"coverage-in-practice\">Coverage In Practice <a href=\"#coverage-in-practice\">\ud83d\udd17<\/a><\/h2><p>It may be unlikely that you use the <code>Coverage<\/code> module directly. However, thereare tools you can use to measure code coverage that builds on this abstraction.<\/p><p>There are many tools for measuring test coverage, but one is <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\">SimpleCov<\/a>. It alsosupports <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov#branch-coverage-ruby--25\">branches coverage<\/a>. To measure coverage of production code, check out<a href=\"https:\/\/github.com\/danmayer\/coverband\">Coverband<\/a>, which you can set up to use <a href=\"https:\/\/github.com\/danmayer\/coverband\/blob\/43c5ac94febc7a961346b0e9408d829d4d2ef8ad\/test\/rails5_dummy\/config\/coverband.rb#L15\">oneshot lines<\/a> mode.<\/p><p>Ruby&rsquo;s coverage module includes many options that can answer different questionsabout your code. What do you think you could use it for in your application?<a href=\"https:\/\/twitter.com\/kevin_j_m\">Let me know<\/a>!<\/p>"},{"title":"Gnarly Endings","link":"https:\/\/kevinjmurphy.com\/posts\/gnarly-endings\/","pubDate":"Fri, 15 Oct 2021 07:00:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/gnarly-endings\/","description":"<p>Today is my last day at <a href=\"https:\/\/www.thegnar.com\/\">The Gnar Company<\/a>. I joinedthe company over five years ago excited to work with a great group ofindividuals, many of whom I&rsquo;d worked with before. Though many of the people havechanged, and the team has grown, the people that work at The Gnar are stillsome of the greatest people I&rsquo;ve had the pleasure to work with.<\/p><p>I&rsquo;m thankful to every member of the team, past and present, who helped makeworking at The Gnar Company the special experience that it is.<\/p>"},{"title":"Code Golf: Conditionally Add To An Array","link":"https:\/\/kevinjmurphy.com\/posts\/code-golf-conditionally-add-to-array\/","pubDate":"Tue, 28 Sep 2021 19:00:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/code-golf-conditionally-add-to-array\/","description":"<h2 id=\"the-grass-is-always-greener\">The Grass Is Always Greener <a href=\"#the-grass-is-always-greener\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re building a system to track a golfer&rsquo;s statuses during a tournament. This isa competitive tournament with people who are much better than I will ever be, soif a golfer is currently scoring under <a href=\"https:\/\/en.wikipedia.org\/wiki\/Par_(score)\">par<\/a>, they&rsquo;re in contention to win. On this 18 hole course, if they&rsquo;ve played the first nine holes, they&rsquo;ve made the <a href=\"https:\/\/www.golfcompendium.com\/2020\/07\/the-turn-definition-golf-course.html\">turn<\/a>.<\/p><p>Let&rsquo;s explore a number of ways we can build up an array that keeps track ofwhich, if any, of these statuses a particular golfer qualifies for. Not contentto settle for one that works, we&rsquo;ll dig into a variety of options.<\/p><h2 id=\"teeing-up-an-option\">Teeing Up An Option <a href=\"#teeing-up-an-option\">\ud83d\udd17<\/a><\/h2><p>We can start with an empty array, and explicitly add in any of the statuses thatthe golfer meets the conditions for.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_statuses<\/span><\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> under_par?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> back_nine?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> statuses<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>There&rsquo;s nothing tremendously exciting here, and that&rsquo;s not a bad thing! It&rsquo;sreasonably clear what this is doing.<\/p><h2 id=\"tapping-it-in\">Tapping It In <a href=\"#tapping-it-in\">\ud83d\udd17<\/a><\/h2><p>We can make the prior suggestion a little more terse by using <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Kernel.html#method-i-tap\">tap<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_statuses<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[].<\/span>tap <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>statuses<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> under_par?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> back_nine?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This eliminates the need for the <code>statuses<\/code> temporary array from the priorsection.<\/p><h2 id=\"taking-a-compact-swing\">Taking a Compact Swing <a href=\"#taking-a-compact-swing\">\ud83d\udd17<\/a><\/h2><p>We can also build our array to have an entry for each of the conditionals wehave, and removing the ones that aren&rsquo;t relevant.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_statuses<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> under_par? ? <span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span> : <span style=\"color:#66d9ef\">nil<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> back_nine? ? <span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span> : <span style=\"color:#66d9ef\">nil<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">].<\/span>compact<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>By using <a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/Array.html#method-i-compact\">compact<\/a>,we&rsquo;ll remove any <code>nil<\/code> values - and we&rsquo;ll take advantage of that functionalityby explicitly adding <code>nil<\/code> if the golfer doesn&rsquo;t meet that condition.<\/p><h2 id=\"working-on-your-backswing-takeaway\">Working On Your Backswing Takeaway <a href=\"#working-on-your-backswing-takeaway\">\ud83d\udd17<\/a><\/h2><p>Speaking of taking things away, we can also do the opposite of the firstapproach. We&rsquo;ll start by having each status, and then <em>removing<\/em> the ones thatdo <strong>not<\/strong> meet the necessary condition.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_statuses<\/span><\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span>, <span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">unless<\/span> under_par?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">-=<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">unless<\/span> back_nine?<\/span><\/span><span style=\"display:flex;\"><span> statuses <span style=\"color:#f92672\">-=<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> statuses<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This may be more helpful in situations where there are a lot more statuses, andonly a few of them may need to be removed for certain reasons. It may also helpwhen the full list of statuses persists on its own elsewhere, but then you alsoneed a subset of them in a particular case.<\/p><h2 id=\"selecting-the-right-club\">Selecting The Right Club <a href=\"#selecting-the-right-club\">\ud83d\udd17<\/a><\/h2><p>Lastly, we&rsquo;ll put together a hash keyed on the statuses where the value is the result of the conditions. We can then flex some familiarity with Ruby&rsquo;s <a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/Enumerable.html\">Enumerable module<\/a> to pick out the applicable sections of the hash, returning only the statuses.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">current_statuses<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;in_contention&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> under_par?,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;past_the_turn&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> back_nine?,<\/span><\/span><span style=\"display:flex;\"><span> }<span style=\"color:#f92672\">.<\/span>select { <span style=\"color:#f92672\">|<\/span>_status, condition<span style=\"color:#f92672\">|<\/span> condition }<span style=\"color:#f92672\">.<\/span>keys<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Similar to the prior suggestion, this may be helpful when you want to have thefull list of statuses and their associated conditions all compiled in one place,but then want to peel off which are relevant for a particular golfer at thistime.<\/p><h2 id=\"asking-help-from-the-caddie\">Asking Help From The Caddie <a href=\"#asking-help-from-the-caddie\">\ud83d\udd17<\/a><\/h2><p>Here are some of the ways that we might solve this problem. What other wayscould we build this functionality? <a href=\"https:\/\/twitter.com\/kevin_j_m\">Let me know<\/a>what other approaches you would take.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/code-golf-conditionally-add-to-array\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Buffered IO Streams In Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-io-buffer\/","pubDate":"Mon, 20 Sep 2021 09:00:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-io-buffer\/","description":"<h2 id=\"object-permanence\">Object Permanence <a href=\"#object-permanence\">\ud83d\udd17<\/a><\/h2><p>We have some really important information in our console.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> data <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;some really important information&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;some really important information&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>We want to store that information to disk, but only temporarily. We&rsquo;ll do sousing a <code>Tempfile<\/code>, which is built in to Ruby, but must be required to be used.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> require <span style=\"color:#e6db74\">&#34;tempfile&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Tempfile<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><\/code><\/pre><\/div><p>This creates a new temporary file on disk, and as it&rsquo;s new, it is currentlyempty, which we&rsquo;ll check two different ways.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>size<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><\/code><\/pre><\/div><p><strong>Finally<\/strong> we can write some really important information to disk, after whichwe&rsquo;ll check the size again.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>write(data)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>size<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we <a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/IO.html#method-i-write\">write<\/a> to theIO stream (in this case a file), we get the number of bytes written, 33,returned. After writing to the file, we asked for the size of the file with<code>File.size<\/code> and got 0. Then, we asked the file for its size and got 33. Whathappened?<\/p><h2 id=\"wheres-that-string\">Where&rsquo;s that string? <a href=\"#wheres-that-string\">\ud83d\udd17<\/a><\/h2><p>Maybe the string was written to the file instance in memory before beingcommitted to disk. Let&rsquo;s look at the <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.1\/libdoc\/objspace\/rdoc\/ObjectSpace.html#method-c-memsize_of\">size<\/a> of our objects.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> require <span style=\"color:#e6db74\">&#34;objspace&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">ObjectSpace<\/span><span style=\"color:#f92672\">.<\/span>memsize_of(data)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">40<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">ObjectSpace<\/span><span style=\"color:#f92672\">.<\/span>memsize_of(file)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">80<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now, <code>memsize_of<\/code> is a hint\/guess - and the docs are clear about that:<\/p><blockquote><p>Note that the return size is incomplete. You need to deal with this information as only a HINT.<\/p><\/blockquote><p>That&rsquo;ll work for us; we&rsquo;ll just use it to see if it changed at all.<\/p><p>Let&rsquo;s try to write again, now that we&rsquo;ve seen the file is currently 80 bytes inmemory.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>write(data)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">ObjectSpace<\/span><span style=\"color:#f92672\">.<\/span>memsize_of(file)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">80<\/span><\/span><\/span><\/code><\/pre><\/div><p>The size of the <code>file<\/code> object itself didn&rsquo;t change, so I guess it&rsquo;s not hidingin there.<\/p><p>As we saw previously, passing the path to <code>File.size<\/code> doesn&rsquo;t show thenewly-written bytes being written to the file, but asking the <code>file<\/code> instanceitself for its <code>size<\/code> does.<\/p><p>Also, after asking for <code>file.size<\/code>, <code>File.size(file.path)<\/code> <strong>does<\/strong> have thesize including the newly-written bytes. So they do <em>eventually<\/em> agree on thefile&rsquo;s size.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>size<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">66<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">66<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"sizing-up-the-difference\">Sizing Up The Difference <a href=\"#sizing-up-the-difference\">\ud83d\udd17<\/a><\/h2><p>Calling <code>size<\/code> on the file instance has a <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.1\/libdoc\/tempfile\/rdoc\/Tempfile.html#method-i-size\">documented<\/a> side effect.<\/p><blockquote><p>As a side effect, the IO buffer is flushed before determining the size.<\/p><\/blockquote><p>That explains where our string went after writing it! It was stored in Ruby&rsquo;sIO buffer. Flushing the buffer <a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/IO.html#method-i-flush\">pushes<\/a>its contents to the operating system.<\/p><p>Let&rsquo;s observe that by checking the size of the file, writing more bytes to it,checking the size of the file doesn&rsquo;t change, and explicitly flushing the buffer.<\/p><p>After flushing the buffer, the size of the file <em>does<\/em> change by the number ofbytes written.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">66<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>write(data)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">66<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>flush<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">99<\/span><\/span><\/span><\/code><\/pre><\/div><p><a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/IO.html#method-i-rewind\">Rewinding<\/a> the fileafter writing it also appears to flush the buffer.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">99<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>write(data)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">33<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">99<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> file<span style=\"color:#f92672\">.<\/span>rewind<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">132<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"no-buffering\">No Buffering <a href=\"#no-buffering\">\ud83d\udd17<\/a><\/h2><p>We can bypass Ruby&rsquo;s IO buffer by setting the stream&rsquo;s <a href=\"https:\/\/ruby-doc.org\/core-3.0.1\/IO.html#method-i-sync\">sync mode<\/a>.By default, this is set to buffer; however, setting it to true will immediatelyflush the stream contents to the operating system.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> new_file <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Tempfile<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> new_file<span style=\"color:#f92672\">.<\/span>sync<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> new_file<span style=\"color:#f92672\">.<\/span>sync <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> streaming <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;no buffering&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(new_file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">0<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> new_file<span style=\"color:#f92672\">.<\/span>write(streaming)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">12<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">File<\/span><span style=\"color:#f92672\">.<\/span>size(new_file<span style=\"color:#f92672\">.<\/span>path)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">12<\/span><\/span><\/span><\/code><\/pre><\/div><p><code>File.size<\/code> is recognizing the bytes in the file without needing to flush thebuffer, either directly or via a method that does so as a side effect. The syncmode is pushing whatever we write directly to disk (at least, through theoperating system).<\/p><h2 id=\"closing-our-stream\">Closing Our Stream <a href=\"#closing-our-stream\">\ud83d\udd17<\/a><\/h2><p>Ruby will buffer writes in an IO stream, such as a file, and you need to bemindful of when or if that buffer is flushed should you then immediately checkthe impact that writing to a stream had on the item being written to.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/ruby-io-buffer\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Best Practices Are The Best For Whom?","link":"https:\/\/kevinjmurphy.com\/posts\/remote-ruby-143\/","pubDate":"Thu, 26 Aug 2021 19:13:09 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/remote-ruby-143\/","description":"<p>I had a great time talking to Andrew Mason, Jason Charnes , and Chris Oliver on Remote Ruby. The episode was just released, and you can listen to it <a href=\"https:\/\/remoteruby.transistor.fm\/143\">here<\/a>.<\/p><p>There&rsquo;s something bothering me that I feel I didn&rsquo;t say all that clearly. I mention that these rules (test coverage, DRY, and performant code are the examples given in the conversation) feel easy, but are hard to apply in practice.<\/p><p>Who am I saying they&rsquo;re easy for? You may think I&rsquo;m talking about the people who we ask to use them. And that may be true - it may be easy, or easier, to follow the rule.<\/p><p>However, that&rsquo;s not what gets me worked up about this. Instead, I think it&rsquo;s easy or easier for the people who <strong>say<\/strong> the rule, and does a disservice to those who we ask to apply it, unless it&rsquo;s backed up with a lot more support.<\/p><h2 id=\"testing-for-coverage\">Testing for coverage <a href=\"#testing-for-coverage\">\ud83d\udd17<\/a><\/h2><p>My thoughts here apply in a lot of scenarios, but let&rsquo;s use a tangible example. I&rsquo;ll choose test coverage because we spend a lot of the episode talking about it, and metrics in general, and I think it&rsquo;s pretty illustrative for what I want to say.<\/p><p>I like writing tests. They give me fast feedback. They allow me to validate my architecture and design. They give me confidence to make future changes. I&rsquo;m bought in. Writing tests is part of my workflow. Having a test coverage bar or metric that we track isn&rsquo;t likely to change the way I write code - at least, not in what I consider a materially valuable way. No, the apps I work with don&rsquo;t have 100% test coverage. My goal is to have sufficient coverage that I feel confident making a change and documenting current behavior. That&rsquo;s a lot harder to quantify - but if you know how to quantify confidence - let me know!<\/p><p>But what about the person who doesn&rsquo;t write tests? Mandating tests will be good for the project, right? People who aren&rsquo;t as familiar with writing tests will do so, and the project will be better off because there are tests now, right? Maybe.<\/p><h2 id=\"impacts-of-best-practices-as-mandates\">Impacts of best practices as mandates <a href=\"#impacts-of-best-practices-as-mandates\">\ud83d\udd17<\/a><\/h2><p>The person who isn&rsquo;t familiar with or comfortable writing tests likely still has ways that they get confidence that their code works or validates their assumptions or gets feedback on their progress. A new tester isn&rsquo;t going to start getting that comfort from tests solely because they have to write them.<\/p><p>Why is the team writing tests now? Because a computer will yell at them if they don&rsquo;t, and\/or their code won&rsquo;t get merged. It&rsquo;s a box to check. Without the training and backing of why testing is important and valuable, and giving people the space to learn, grow, and experiment - they can resent it.<\/p><p>What good are the tests you have then? They made your metric look good, but what are you going to use them for? It&rsquo;s unclear, beyond demonstrating that they exist and giving your CI provider more money if you&rsquo;re billed by minute usage.<\/p><p>Are these tests going to help you catch a bug before it gets into production, or fix one that&rsquo;s already there? Did these tests help inform the choices developers made while writing the code that accompanies the test? Did these tests allow for immediate, small steps with fast feedback?<\/p><p>I don&rsquo;t know - and neither do you probably. Those things are hard to measure. They&rsquo;re also a lot of the value people believe they get out of testing. Capturing or enforcing test coverage metrics in a vacuum does nothing to help inform how much better off you are than if you had no tests at all. It&rsquo;s a proxy measure that&rsquo;s difficult to draw conclusions from without digging deeper.<\/p><h2 id=\"why-bother-following-the-rules\">Why bother following the rules? <a href=\"#why-bother-following-the-rules\">\ud83d\udd17<\/a><\/h2><p>This doesn&rsquo;t mean don&rsquo;t write tests. It can give you insights you didn&rsquo;t have before! Chris Oliver talked about the pay gem, and how different vendors may support features differently, or not at all. Testing can reveal that to you in a quicker\/safer way than other explorations. Not only because a test may fail - but because the work Chris does, and things he researched, in order to have that test fail, gives more information about how the vendor&rsquo;s feature works. If you want to know more about that, I have another talk about testing (and designing interactions with) third-party dependencies <a href=\"https:\/\/kevinjmurphy.com\/posts\/railsconf-2020\/\">here<\/a>.<\/p><p>I&rsquo;m on &ldquo;team testing&rdquo;. My process of getting there came from a lot of opportunities to learn from testing, experiencing it, and having the chance to be &ldquo;saved&rdquo; by them. Forcing tests may increase the likelihood those things happen, but I think you&rsquo;ll have a harder time convincing people on the value of tests if they exist to appease a number.<\/p><p>A test you wish you&rsquo;d written may have more impact on your career than one you wrote perfectly. The experience of not having that safety net, of not having that immediate feedback, and coming to terms with the consequences of that over the life of a project will stick with you. That doesn&rsquo;t mean don&rsquo;t write tests. But recognize it might not click for someone until they <em>know<\/em> this concept exists, they <em>know<\/em> they didn&rsquo;t use it, and now they <em>know<\/em> how much worse off they are right now without it.<\/p><p>This doesn&rsquo;t mean don&rsquo;t track test coverage. It can be motivating! Jason Charnes mentioned using it to drive an increase in coverage on a project. Once he got the value he sought out of the tests, the metric\/number itself didn&rsquo;t matter as much, so he moved on. Jason got the benefit he wanted, and the metric may have helped drive him there, but I&rsquo;m betting that the benefit wasn&rsquo;t seeing a bigger number on his test coverage %.<\/p><h2 id=\"put-in-the-work\">Put in the work <a href=\"#put-in-the-work\">\ud83d\udd17<\/a><\/h2><p>It&rsquo;s easy to say these rules and walk away feeling like we&rsquo;ve shared some wisdom. What&rsquo;s harder is putting in the work to help a person grow so they happen upon their own wisdom. We need to have support structures in place so people can learn about these things, and wrestle with them, and gain familiarity. Then they&rsquo;re not rules to be followed - they become shared values we benefit from, and they&rsquo;re used when we see a benefit.<\/p><p>Thanks Andrew, Jason, and Chris for having me on.<\/p>"},{"title":"Temporary State With Blocks Post Sharing","link":"https:\/\/kevinjmurphy.com\/posts\/temporary-state-blocks-sharing\/","pubDate":"Sun, 22 Aug 2021 19:13:09 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/temporary-state-blocks-sharing\/","description":"<p>Last week, I wrote a <a href=\"https:\/\/kevinjmurphy.com\/posts\/temporary-state-in-tests\/\">post<\/a> on myemployer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/temporary-state-in-tests\">blog<\/a> about using blocks in ruby to temporarily alter the state ofthe application, and how that can be particularly helpful in tests.<\/p><p>That post was featured in Ruby Radar <a href=\"https:\/\/rubyradar.dev\/issues\/ruby-radar-12-back-to-our-regularly-scheduled-programming-726989\">#12<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_radar_12.png\"><\/figure><p>This post was also featured in Ruby Weekly <a href=\"https:\/\/rubyweekly.com\/issues\/568\">#568<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_568.png\"><\/figure>"},{"title":"Temporary State in Tests: Bloc(k) Party","link":"https:\/\/kevinjmurphy.com\/posts\/temporary-state-in-tests\/","pubDate":"Sat, 14 Aug 2021 14:05:07 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/temporary-state-in-tests\/","description":"<h2 id=\"testing-around-the-clock-sundayhttpsyoutubeveqiwt9ejdy\">Testing Around the Clock (<a href=\"https:\/\/youtu.be\/vEqIWT9EjDY\">Sunday<\/a>) <a href=\"#testing-around-the-clock-sundayhttpsyoutubeveqiwt9ejdy\">\ud83d\udd17<\/a><\/h2><p>Tests will sometimes require us to bend the truth and modifythe state of the world in order to have repeatable, consistent results andfeedback.<\/p><p>We&rsquo;re building an application that sends a notification on Sunday. Inorder to test it sending a notification, we need it to be Sunday. The problemwith that is I don&rsquo;t really like working on Sundays.<\/p><p>If I&rsquo;m writing code on Wednesday, I want to make sure I don&rsquo;t breaknotifications. Rather than waiting until Sunday to run my tests, I can trick mytest into thinking it&rsquo;s Sunday. Rails has built-in <a href=\"https:\/\/api.rubyonrails.org\/v5.2.6\/classes\/ActiveSupport\/Testing\/TimeHelpers.html\">helpers<\/a>we can use to simulate that.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>travel_to <span style=\"color:#66d9ef\">Time<\/span><span style=\"color:#f92672\">.<\/span>zone<span style=\"color:#f92672\">.<\/span>local(<span style=\"color:#ae81ff\">2021<\/span>, <span style=\"color:#ae81ff\">8<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">34<\/span>)<\/span><\/span><span style=\"display:flex;\"><span>notifier <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Notifier<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>expect(notifier<span style=\"color:#f92672\">.<\/span>send_notification?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><\/code><\/pre><\/div><p>When we do this, we want to ensure that the results are <em>temporary<\/em>. Othertests, or even areas within a given test, shouldn&rsquo;t be affected by this statechange. It should still be whatever day the tests are <strong>really<\/strong> run otherwise.<\/p><p>Now, in the case of these helpers from Rails, according to the documentation:<\/p><blockquote><p>The stubs are automatically removed at the end of the test.<\/p><\/blockquote><p>However, we want to be clear to readers who may not have perused the Railsdocumentation (nor have encyclopedic knowledge of it). Additionally, we may onlywant part of the test to think it&rsquo;s Sunday. In order to isolate this change, wecan limit its impact to be within a block.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>notifier <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Notifier<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>travel_to <span style=\"color:#66d9ef\">Time<\/span><span style=\"color:#f92672\">.<\/span>zone<span style=\"color:#f92672\">.<\/span>local(<span style=\"color:#ae81ff\">2021<\/span>, <span style=\"color:#ae81ff\">8<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">34<\/span>) <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(notifier<span style=\"color:#f92672\">.<\/span>send_notification?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>travel_to <span style=\"color:#66d9ef\">Time<\/span><span style=\"color:#f92672\">.<\/span>zone<span style=\"color:#f92672\">.<\/span>local(<span style=\"color:#ae81ff\">2021<\/span>, <span style=\"color:#ae81ff\">8<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">1<\/span>, <span style=\"color:#ae81ff\">2<\/span>, <span style=\"color:#ae81ff\">34<\/span>) <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(notifier<span style=\"color:#f92672\">.<\/span>send_notification?)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"benefits-of-the-block-little-thoughtshttpsyoutubeq6cs26oex64\">Benefits of the Block (<a href=\"https:\/\/youtu.be\/q6cS26oEX64\">Little Thoughts<\/a>) <a href=\"#benefits-of-the-block-little-thoughtshttpsyoutubeq6cs26oex64\">\ud83d\udd17<\/a><\/h2><p>The block helps visually distinguish the scope of the change, in addition toensuring that the change is limited to the code within the block. Ifind it to be more readable and clear using a block. We have temporarilychanged the state of the world, but only in a limited capacity.<\/p><p>As far as thetest is concerned, it&rsquo;s Sunday in the first block, Monday in the second block,and whatever day of the week you&rsquo;re reading this before and after the blocks.<\/p><h2 id=\"disregarding-bullet-recommendations-real-talkhttpsyoutubemmmmoriaooc\">Disregarding Bullet Recommendations (<a href=\"https:\/\/youtu.be\/mmMmoRiAoOc\">Real Talk<\/a>) <a href=\"#disregarding-bullet-recommendations-real-talkhttpsyoutubemmmmoriaooc\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;ve used <a href=\"https:\/\/github.com\/flyerhzm\/bullet\">Bullet<\/a> before for eager loading optimization, you may have runinto situations in which you wished it wouldn&rsquo;t tell you something either needed ordidn&rsquo;t need to be eagerly loaded.<\/p><p>Now, some times that&rsquo;s the tool doing its job: giving you feedback you may nothave expected (or wanted). However, in tests, it may be giving you a falsepositive because of your test setup.<\/p><p>In a test that calls a method where only one record exists in your database,Bullet may suggest that you don&rsquo;t need the eager loading. And Bullet iscorrect - in this case, for this test. However, the setup in that test may notreflect reality or the expectation most of the time. It&rsquo;s much more likely thatthere are <strong>many<\/strong> records, which will cause Bullet to recommend the <code>includes<\/code>were you to get rid of it.<\/p><p>How can we keep our test and satisfy Bullet? We don&rsquo;t; instead, let&rsquo;s turnBullet off. But, we don&rsquo;t want Bullet to stay off for the rest of the test run.We only want it off when calling that one method in this one test.<\/p><p>If you&rsquo;re using RSpec, you can use tags to <a href=\"https:\/\/tosbourn.com\/ignore-bullet-rspec\/\">turn Bullet off<\/a>for an entire test, file, or context. It&rsquo;s a clever and succinct approach.<\/p><p>This is a post about blocks, so let&rsquo;s discuss another way.<\/p><h2 id=\"turn-it-off-and-on-again-fluxhttpsyoutubettcboe1grng\">Turn It Off And On Again (<a href=\"https:\/\/youtu.be\/ttcboE1GrNg\">Flux<\/a>) <a href=\"#turn-it-off-and-on-again-fluxhttpsyoutubettcboe1grng\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;ve discussed how to <strong>use<\/strong> a method that takes a block to temporarilychange state in our application, specifically tests. Let&rsquo;s start by writing thecode we want:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;doesn&#39;t complain about Bullet warnings&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> disable_bullet <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">Notification<\/span><span style=\"color:#f92672\">.<\/span>method_that_eager_loads_associations<span style=\"color:#f92672\">.<\/span>size)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Let&rsquo;s write an implementation of <code>disable_bullet<\/code>. We can use <code>yield<\/code> to wrapturning Bullet off and on again inside of <code>disable_bullet<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">disable_bullet<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">yield<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Calling this method turns Bullet off, runs whatever code is in the block passedto it, and then turns Bullet back on after the block completes. We can make theblock optional by calling the <code>block_given?<\/code> method:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">disable_bullet<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">yield<\/span> <span style=\"color:#66d9ef\">if<\/span> block_given?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, we&rsquo;re going to remove that in this case because there&rsquo;s no use case forcalling this method otherwise. We&rsquo;ll get an error without passing a block to<code>disable_bullet<\/code>, and that&rsquo;s ok.<\/p><h2 id=\"why-did-i-do-this-again-i-still-rememberhttpsyoutube5r-9igwd36a\">Why Did I Do This Again? (<a href=\"https:\/\/youtu.be\/5R-9IgWD36A\">I Still Remember<\/a>) <a href=\"#why-did-i-do-this-again-i-still-rememberhttpsyoutube5r-9igwd36a\">\ud83d\udd17<\/a><\/h2><p>When you&rsquo;re writing the test that you need to disable Bullet, you likely havethe context of why Bullet needed to be turned off. However, when you&rsquo;re readingit later, you may forget. And maybe someone else wrote this test and youdon&rsquo;t know to begin with. Is it <em>reasonable<\/em> to have turned Bullet off? We canforce this to be documented in the test by making an addition to our method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">disable_bullet<\/span>(<span style=\"color:#e6db74\">reason<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">false<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Bullet<\/span><span style=\"color:#f92672\">.<\/span>enable <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Callers are now required to pass in a <strong>reason<\/strong> why Bullet has beenturned off. It&rsquo;s not used in the method, but it helps enforce a thoughtfulunderstanding of why this tool is being turned off when the method iscalled.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>it <span style=\"color:#e6db74\">&#34;doesn&#39;t complain about Bullet warnings&#34;<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> disable_bullet(<span style=\"color:#e6db74\">reason<\/span>: <span style=\"color:#e6db74\">&#34;Trust me. But seriously, here&#39;s why...&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> expect(<span style=\"color:#66d9ef\">Notification<\/span><span style=\"color:#f92672\">.<\/span>method_that_eager_loads_associations<span style=\"color:#f92672\">.<\/span>size)<span style=\"color:#f92672\">.<\/span>to eq <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>By using a block here, we have provided a way to <em>temporarily<\/em> change the stateof the world with an aesthetic that clearly denotes the scope of the change, andwe also added a way to force users to justify their use of the method tothemselves and others.<\/p><h2 id=\"inspiration-pioneershttpsyoutubejrpmikap2ss\">Inspiration (<a href=\"https:\/\/youtu.be\/JrpMIkaP2ss\">Pioneers<\/a>) <a href=\"#inspiration-pioneershttpsyoutubejrpmikap2ss\">\ud83d\udd17<\/a><\/h2><p>Thanks to <a href=\"https:\/\/twitter.com\/purinkle\">Rob Whittaker<\/a>for <a href=\"https:\/\/twitter.com\/purinkle\/status\/1412071536819851268\">asking me<\/a> aboutother ways I like to use blocks, based on an <a href=\"https:\/\/kevinjmurphy.com\/posts\/activerecord-new-block\/\">earlier post<\/a>.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/temporary-state-in-tests\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"CSV Validation Post Sharing","link":"https:\/\/kevinjmurphy.com\/posts\/csv-validation-sharing\/","pubDate":"Tue, 27 Jul 2021 19:13:09 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/csv-validation-sharing\/","description":"<p>Last week, I wrote a <a href=\"https:\/\/kevinjmurphy.com\/posts\/active-model-validations-csv\/\">post<\/a> on myemployer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/active-model-validations-csv\">blog<\/a> aboutvalidating input from a CSV file using <code>ActiveModel::Validations<\/code>.<\/p><p>That post was featured in Ruby Weekly <a href=\"https:\/\/rubyweekly.com\/issues\/562\">#562<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_562.png\"><\/figure><p>It was also shared in Awesome Ruby <a href=\"https:\/\/ruby.libhunt.com\/newsletter\/270\">#270<\/a>. This is the first time I&rsquo;ve been inAwesome Ruby!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/awesome_ruby_270.png\"><\/figure><p>All of my blog posts are cross-posted on <a href=\"https:\/\/dev.to\/kevin_j_m\">dev.to<\/a>,and their ruby community was nice enough to share my post with their twitterfollowers.<\/p><p>The tweet says:<\/p><blockquote><p>Validate CSV Input With ActiveModel::Validations<\/p><p>{ author: @kevin_j_m } #DEVCommunity<\/p><\/blockquote><p>Lastly, Ruby Radar <a href=\"https:\/\/rubyradar.dev\/issues\/ruby-radar-9-junior-spotlight-705164\">#9<\/a>featured the post!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_radar_9.png\"><\/figure>"},{"title":"Validate CSV Input With ActiveModel::Validations","link":"https:\/\/kevinjmurphy.com\/posts\/active-model-validations-csv\/","pubDate":"Mon, 19 Jul 2021 08:08:53 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/active-model-validations-csv\/","description":"<h2 id=\"standard-library-parsing\">Standard Library Parsing <a href=\"#standard-library-parsing\">\ud83d\udd17<\/a><\/h2><p>We&rsquo;re maintaining a system that tracks information about books, including theirpublication dates. On occasion, publishers will send us CSVs with updatedpublication dates, and we need to update our Rails application to have thosedates.<\/p><p>We want a repeatable process, so rather than updating these by hand, let&rsquo;s useRuby&rsquo;s <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.1\/libdoc\/csv\/rdoc\/CSV.html\">CSV class<\/a>to parse this data.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">CSV<\/span><span style=\"color:#f92672\">.<\/span>parse(csv_text, <span style=\"color:#e6db74\">headers<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>row<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> book <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Book<\/span><span style=\"color:#f92672\">.<\/span>find_by(<span style=\"color:#e6db74\">isbn<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;ISBN&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> book<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">publication_date<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Pub Date&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Four lines and we have a functionally complete parser that updates our systemhow we expect. That all seems great. Until, that is, we actually run it.<\/p><h2 id=\"book-checked-out\">Book Checked Out <a href=\"#book-checked-out\">\ud83d\udd17<\/a><\/h2><p>We execute our parser on the first data file we receive, and it quickly stopswith the following error:<\/p><pre tabindex=\"0\"><code>NoMethodError: undefined method `update&#39; for nil:NilClass<\/code><\/pre><p>On inspecting the state of our database, we see that the first three books inthe CSV file had their publication dates updated, but the rest didn&rsquo;t. Lookingat the fourth row in the CSV, we discover that the ISBN for that row isn&rsquo;t inour database. In that case <code>find_by<\/code> returns <code>nil<\/code>, and calling <code>update<\/code> on<code>nil<\/code> is exactly our problem. An exception is raised, and further rows of theCSV aren&rsquo;t parsed.<\/p><p>We can fix that! If we don&rsquo;t find the book, let&rsquo;s log the error and move on tothe next row, without calling <code>update<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">CSV<\/span><span style=\"color:#f92672\">.<\/span>parse(csv_text, <span style=\"color:#e6db74\">headers<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>row<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> book <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Book<\/span><span style=\"color:#f92672\">.<\/span>find_by(<span style=\"color:#e6db74\">isbn<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;ISBN&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> book<span style=\"color:#f92672\">.<\/span>blank?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>error(<span style=\"color:#e6db74\">&#34;Could not find book&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">next<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> book<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">publication_date<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Pub Date&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now the entire CSV processes, our books are updated, and everyone&rsquo;s happy.Right?<\/p><h2 id=\"blank-pages\">Blank Pages <a href=\"#blank-pages\">\ud83d\udd17<\/a><\/h2><p>Days later, we discover that a book which previously had a publication datedoesn&rsquo;t anymore. It&rsquo;s not unusual for a book to not have a publication date - wehave records of books that haven&rsquo;t been published yet. However, books shouldn&rsquo;t<em>lose<\/em> an existing publication date.<\/p><p>We see that the book in question was in the CSV, and the Pub Date column wasempty for that row. Turns out, that was an error from the publisher in preparingthe CSV. Any book in that file should always have a publication date - thepurpose of this process is to provide publication dates.<\/p><p>We can prevent this from happening in the future by ensuring that a row has apublication date before attempting to process it:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">CSV<\/span><span style=\"color:#f92672\">.<\/span>parse(csv_text, <span style=\"color:#e6db74\">headers<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>row<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Pub Date&#34;<\/span><span style=\"color:#f92672\">].<\/span>blank?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>error(<span style=\"color:#e6db74\">&#34;Skipped updating book with no publication date&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">next<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> book <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Book<\/span><span style=\"color:#f92672\">.<\/span>find_by(<span style=\"color:#e6db74\">isbn<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;ISBN&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> book<span style=\"color:#f92672\">.<\/span>blank?<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>error(<span style=\"color:#e6db74\">&#34;Could not find book&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">next<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> book<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">publication_date<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Pub Date&#34;<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"losing-the-plot\">Losing The Plot <a href=\"#losing-the-plot\">\ud83d\udd17<\/a><\/h2><p>Our &ldquo;simple&rdquo; parser is now a lot more complicated. Business rules about theshape, structure, and expectations of the data are now littered alongwith actions consuming the data to do things like find the book and update itwith the appropriate publication date. It&rsquo;s harder to identify what the coreresponsibility of this snippet of code is.<\/p><h2 id=\"adding-chapters\">Adding Chapters <a href=\"#adding-chapters\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s try to increase the clarity of our code by separating out how to processan individual row of the CSV.<\/p><h3 id=\"first-act-establishing-a-new-character-class\">First Act: Establishing A New Character (Class) <a href=\"#first-act-establishing-a-new-character-class\">\ud83d\udd17<\/a><\/h3><p>We&rsquo;ll start by making a class that takes in the needed data from the CSV row andfinds the book associated with the ISBN.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">BookPublicationDateImportRow<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:isbn<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">:publication_date<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(<span style=\"color:#e6db74\">isbn<\/span>:, <span style=\"color:#e6db74\">publication_date<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @isbn <span style=\"color:#f92672\">=<\/span> isbn<\/span><\/span><span style=\"display:flex;\"><span> @publication_date <span style=\"color:#f92672\">=<\/span> publication_date<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">book<\/span><\/span><\/span><span style=\"display:flex;\"><span> @book <span style=\"color:#f92672\">||=<\/span> <span style=\"color:#66d9ef\">Book<\/span><span style=\"color:#f92672\">.<\/span>find_by(<span style=\"color:#e6db74\">isbn<\/span>: isbn)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Perhaps you&rsquo;ve heard of a <a href=\"https:\/\/thoughtbot.com\/blog\/activemodel-form-objects\">form object<\/a>to represent data associated with a particular form on your web application. Youcan consider that&rsquo;s what we&rsquo;re doing here, except our input isn&rsquo;t from a form ona web page - it&rsquo;s a row from a CSV file.<\/p><h3 id=\"second-act-rising-validations\">Second Act: Rising Validations <a href=\"#second-act-rising-validations\">\ud83d\udd17<\/a><\/h3><p>Now, rather than rewriting validation logic, as we had in our procedural codeinitially, we can use ActiveModel&rsquo;s <a href=\"https:\/\/api.rubyonrails.org\/v6.1.3.1\/classes\/ActiveModel\/Validations.html\">Validations<\/a>module. That&rsquo;ll allow us access to the validation <a href=\"https:\/\/guides.rubyonrails.org\/active_record_validations.html#validation-helpers\">helpers<\/a>we&rsquo;re used to using with other Rails ActiveRecord models.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">BookPublicationDateImportRow<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">include<\/span> <span style=\"color:#66d9ef\">ActiveModel<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Validations<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:book<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> validates <span style=\"color:#e6db74\">:publication_date<\/span>, <span style=\"color:#e6db74\">presence<\/span>: <span style=\"color:#66d9ef\">true<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We would have caught our last problem of losing publication dates if thatvalidation were on the Book model itself - and we may be tempted to look to addit now. However, remember - having a book with no publication date is totallynormal for our application. It&rsquo;s only in <strong>this<\/strong> instance of receiving apublication date update from a publisher with no publication date where it&rsquo;sunacceptable to have a value for that attribute.<\/p><h3 id=\"third-act-update-resolution\">Third Act: Update Resolution <a href=\"#third-act-update-resolution\">\ud83d\udd17<\/a><\/h3><p>We&rsquo;ll also mirror ActiveRecord&rsquo;s API by adding in a <code>save<\/code> method that makessure our instance is passing its own validations before updating the book:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">BookPublicationDateImportRow<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">save<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">return<\/span> <span style=\"color:#66d9ef\">false<\/span> <span style=\"color:#66d9ef\">unless<\/span> valid?<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> book<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">publication_date<\/span>: @publication_date)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"rewriting-the-intro\">Rewriting The Intro <a href=\"#rewriting-the-intro\">\ud83d\udd17<\/a><\/h2><p>Now that we have something that&rsquo;s responsible for managing an individual row, wecan update our parsing code to be responsible for iterating through that CSV andpass off the details of how to manage that data to our new class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">CSV<\/span><span style=\"color:#f92672\">.<\/span>parse(csv_text, <span style=\"color:#e6db74\">headers<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>row<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> book_import <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">BookPublicationDateImportRow<\/span><span style=\"color:#f92672\">.<\/span>new(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">isbn<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;ISBN&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">publication_date<\/span>: row<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Pub Date&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> <span style=\"color:#f92672\">!<\/span>book_import<span style=\"color:#f92672\">.<\/span>save<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rails<\/span><span style=\"color:#f92672\">.<\/span>logger<span style=\"color:#f92672\">.<\/span>error(book_import<span style=\"color:#f92672\">.<\/span>errors<span style=\"color:#f92672\">.<\/span>full_messages<span style=\"color:#f92672\">.<\/span>join(<span style=\"color:#e6db74\">&#34;, &#34;<\/span>))<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"epilogue\">Epilogue <a href=\"#epilogue\">\ud83d\udd17<\/a><\/h2><p>By adding this new class, we&rsquo;ve given ourselves an extension point foradditional logic on the row. Any data transformation, like converting thepublication date string to a date object, can be handled here (however, for CSVparsing, do take a look at the standard library&rsquo;s <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.1\/libdoc\/csv\/rdoc\/CSV.html#class-CSV-label-Built-In+Field+Converters\">converters<\/a>as well!).<\/p><p>Any further validations that need to be exercised on the data can take place inthis separate class. Moreover, we can use framework features and concepts thatwe&rsquo;re already familiar with, rather than rewriting our own business logic forvalidation.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/active-model-validations-csv\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Appearance on Rails With Jason Podcast","link":"https:\/\/kevinjmurphy.com\/posts\/rails-with-jason-podcast\/","pubDate":"Thu, 17 Jun 2021 11:45:52 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rails-with-jason-podcast\/","description":"<p>Jason Swett was kind enough to him me on his podcast, <a href=\"https:\/\/www.codewithjason.com\/rails-with-jason-podcast\/\">Rails With Jason<\/a>. We discussed the contents of my RailsConf 2021 talk: Engineering MBA.<\/p><p>Thanks for having me on Jason! You can give it a listen <a href=\"https:\/\/www.codewithjason.com\/rails-with-jason-podcast\/episodes\/096-engineering-mba-with-kevin-murphy-jumSoete\/\">here<\/a>.<\/p>"},{"title":"Ruby Weekly 554","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-554\/","pubDate":"Fri, 28 May 2021 07:50:04 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-554\/","description":"<p>I&rsquo;m excited to be in this week&rsquo;s Ruby Weekly, <a href=\"https:\/\/rubyweekly.com\/issues\/554\">#554<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_554.png\"><\/figure><p>The article they included is a <a href=\"https:\/\/kevinjmurphy.com\/posts\/activerecord-new-block\/\">post<\/a>from my employer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/activerecord-new-block\">blog<\/a>about passing a block to ActiveRecord methods, and how it differs from using<code>tap<\/code>.<\/p>"},{"title":"RailsConf Roundup 2021","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-roundup-2021\/","pubDate":"Wed, 19 May 2021 10:50:20 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-roundup-2021\/","description":"<h1 id=\"railsconf-2021\">RailsConf 2021 <a href=\"#railsconf-2021\">\ud83d\udd17<\/a><\/h1><p><a href=\"https:\/\/railsconf.com\/\">RailsConf<\/a> wrapped up in mid-April with a virtualgathering, featuring attendees from 61 different countries. The talks are nowavailable to all on <a href=\"https:\/\/www.youtube.com\/c\/rubycentral\/videos\">YouTube<\/a>, soif you&rsquo;re looking for some suggestions on where to start, please allow me tocurate a list for you.<\/p><h2 id=\"recommended-talks\">Recommended Talks <a href=\"#recommended-talks\">\ud83d\udd17<\/a><\/h2><h3 id=\"refactoring-a-developers-guide-to-writing-well\">Refactoring: A developer&rsquo;s guide to writing well <a href=\"#refactoring-a-developers-guide-to-writing-well\">\ud83d\udd17<\/a><\/h3><p>This is my favorite session that I&rsquo;ve had the opportunity to watch thus far.Writing is such a critical component of our jobs (and lives), and this primer ispresented in such a friendly manner.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/BbIILUSmSk4\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"hiring-is-not-hazing\">Hiring is Not Hazing <a href=\"#hiring-is-not-hazing\">\ud83d\udd17<\/a><\/h3><p>If you&rsquo;re associated with a tech company, and reading this around the time thatthis published - you&rsquo;re probably hiring. Whether you are or not, take thisopportunity to evaluate the process you use to lead to better outcomes forcandidates and the organization.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/KLHYZT2I28k\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"missing-guide-to-service-objects-in-rails\">Missing Guide to Service Objects in Rails <a href=\"#missing-guide-to-service-objects-in-rails\">\ud83d\udd17<\/a><\/h3><p>&ldquo;Service objects&rdquo; as a concept means so many different things to differentpeople. This talk introduces different philosophies people take in constructingservice objects and presents the trade-offs of each.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/XH1fbvexqyU\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"a-day-in-the-life-of-a-ruby-object\">A Day in the Life of a Ruby Object <a href=\"#a-day-in-the-life-of-a-ruby-object\">\ud83d\udd17<\/a><\/h3><p>Garbage collection, mark and sweep, page size, memory allocation - all conceptsthat can feel very intimidating. However, when presented so masterfullyeloquently, this talk is a great way to make these topics feel moreapproachable.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/PuNbdfdFBjk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"viewcomponents-in-the-real-world\">ViewComponents in the Real World <a href=\"#viewcomponents-in-the-real-world\">\ud83d\udd17<\/a><\/h3><p>Encapsulating view logic and presentation in <a href=\"https:\/\/viewcomponent.org\/\">ViewComponents<\/a> helps promote reusable and testable objects to the view layer of your Rails app.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/QoetqsBCsbE\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"speed-up-your-test-suite-by-throwing-computers-at-it\">Speed up your test suite by throwing computers at it <a href=\"#speed-up-your-test-suite-by-throwing-computers-at-it\">\ud83d\udd17<\/a><\/h3><p>This has a number of keen insights on improving the performance of your overalltest suite.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/T3TipTdx_2k\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"engineering-mba-be-the-boss-of-your-own-work\">Engineering MBA: Be The Boss of Your Own Work <a href=\"#engineering-mba-be-the-boss-of-your-own-work\">\ud83d\udd17<\/a><\/h3><p>I&rsquo;ll be honest: I did not actually watch this talk. But let&rsquo;s just say I&rsquo;m<strong>very<\/strong> familiar with the subject matter and the presentation.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/6ldCGo29w5g\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h2 id=\"talks-i-look-forward-to\">Talks I Look Forward To <a href=\"#talks-i-look-forward-to\">\ud83d\udd17<\/a><\/h2><p>I did not have the opportunity to watch everything I wanted to during theconference. Here is a small sampling of what&rsquo;s still on my list.<\/p><h3 id=\"the-history-of-making-mistakes\">The History of Making Mistakes <a href=\"#the-history-of-making-mistakes\">\ud83d\udd17<\/a><\/h3><p>As someone that has <em>certainly<\/em> never made a mistake before, I can&rsquo;t wait tolearn what all the fuss is about.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/har1kdHqCDw\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"how-reference-librarians-can-help-us-help-each-other\">How Reference Librarians Can Help Us Help Each Other <a href=\"#how-reference-librarians-can-help-us-help-each-other\">\ud83d\udd17<\/a><\/h3><p>Anything that talks about how awesome librarians are is ok by me. I look forwardto hearing more about how I can approach my day-to-day work with the mindset andtools that a reference librarian brings to bear in their profession.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/A2Tr0DWfwAE\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><h3 id=\"what-the-fork\">What the fork()? <a href=\"#what-the-fork\">\ud83d\udd17<\/a><\/h3><p>Need something to be faster? We&rsquo;ll fork a process and it&rsquo;ll magically be better,because concurrency&hellip;right? I&rsquo;ll let you know after I give this talk a view.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/4crIuSj-irQ\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/railsconf-roundup-2021\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"ActiveRecord's New Takes a Block, Kid","link":"https:\/\/kevinjmurphy.com\/posts\/activerecord-new-block\/","pubDate":"Fri, 14 May 2021 12:20:07 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/activerecord-new-block\/","description":"<h2 id=\"making-new-activerecord-models-lets-try-it-againhttpsenwikipediaorgwikilet27s_try_it_again\">Making New ActiveRecord Models (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Let%27s_Try_It_Again\">Let&rsquo;s Try It Again<\/a>) <a href=\"#making-new-activerecord-models-lets-try-it-againhttpsenwikipediaorgwikilet27s_try_it_again\">\ud83d\udd17<\/a><\/h2><p>If we want to make a new instance of an ActiveRecord model with particularattributes, we have a number of options.<\/p><p>We can pass the attributes in as a hash:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>u <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>We can set the attributes after creating the object:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>u <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>u<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>u<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>And there&rsquo;s a third option - we can <em>also<\/em> pass <code>new<\/code> a block:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>u <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>user<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"when-could-we-use-this-the-blockhttpsenwikipediaorgwikithe_block_album\">When Could We Use This? (<a href=\"https:\/\/en.wikipedia.org\/wiki\/The_Block_(album)\">The Block<\/a>) <a href=\"#when-could-we-use-this-the-blockhttpsenwikipediaorgwikithe_block_album\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s say we have a system that members of a band use to check their tourschedule. Band members are users, and when we add a member, we want to make auser for them.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_member<\/span>(<span style=\"color:#e6db74\">first_name<\/span>:, <span style=\"color:#e6db74\">last_name<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @members <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">first_name<\/span>: first_name, <span style=\"color:#e6db74\">last_name<\/span>: last_name)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Additionally, users have a username attribute, and we want to keep that uniquewithin a given band. We also want the system to define the username when we adda band member.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_member<\/span>(<span style=\"color:#e6db74\">first_name<\/span>:, <span style=\"color:#e6db74\">last_name<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> username <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>band_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">_<\/span><span style=\"color:#e6db74\">#{<\/span>last_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> @members<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:last_name<\/span>)<span style=\"color:#f92672\">.<\/span>include?(last_name)<\/span><\/span><span style=\"display:flex;\"><span> username <span style=\"color:#f92672\">&lt;&lt;<\/span> unique_value<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @members <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">first_name<\/span>: first_name,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">last_name<\/span>: last_name,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">username<\/span>: username,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">unique_value<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, if we prefer the aesthetic, we can also define those attributes in ablock:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_member<\/span>(<span style=\"color:#e6db74\">first_name<\/span>:, <span style=\"color:#e6db74\">last_name<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @members <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>user<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> first_name<\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> last_name<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>username <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>band_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">_<\/span><span style=\"color:#e6db74\">#{<\/span>last_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> @members<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:last_name<\/span>)<span style=\"color:#f92672\">.<\/span>include?(last_name)<\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>username <span style=\"color:#f92672\">&lt;&lt;<\/span> unique_value<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"seeing-the-result-the-right-stuffhttpsenwikipediaorgwikiyou_got_it_the_right_stuff\">Seeing The Result (<a href=\"https:\/\/en.wikipedia.org\/wiki\/You_Got_It_(The_Right_Stuff)\">The Right Stuff<\/a>) <a href=\"#seeing-the-result-the-right-stuffhttpsenwikipediaorgwikiyou_got_it_the_right_stuff\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s check our work to see the usernames of our band members.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">]<\/span> pry(main)<span style=\"color:#f92672\">&gt;<\/span> band <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Band<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">&#34;New Kids on the Block&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">]<\/span> pry(main)<span style=\"color:#f92672\">&gt;<\/span> band<span style=\"color:#f92672\">.<\/span>add_member(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">3<\/span><span style=\"color:#f92672\">]<\/span> pry(main)<span style=\"color:#f92672\">&gt;<\/span> band<span style=\"color:#f92672\">.<\/span>add_member(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Donnie&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Wahlberg&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">4<\/span><span style=\"color:#f92672\">]<\/span> pry(main)<span style=\"color:#f92672\">&gt;<\/span> band<span style=\"color:#f92672\">.<\/span>add_member(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">]<\/span> pry(main)<span style=\"color:#f92672\">&gt;<\/span> band<span style=\"color:#f92672\">.<\/span>members<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:username<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;New_Kids_on_the_Block_Knight&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;New_Kids_on_the_Block_Wahlberg&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;New_Kids_on_the_Block_Knight_65&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Jonathan&rsquo;s username has additional characters appended to it, as Jordan alreadyclaimed the username <code>&quot;New_Kids_on_the_Block_Knight&quot;<\/code>.<\/p><h2 id=\"tap-dance-step-by-stephttpsenwikipediaorgwikistep_by_step_new_kids_on_the_block_song\">Tap Dance (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Step_by_Step_(New_Kids_on_the_Block_song)\">Step By Step<\/a>) <a href=\"#tap-dance-step-by-stephttpsenwikipediaorgwikistep_by_step_new_kids_on_the_block_song\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;re familiar with Ruby&rsquo;s <code>tap<\/code> <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Kernel.html#method-i-tap\">method<\/a>,you might be wondering what all the fuss is about. We can do the same thingwith <code>tap<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_member<\/span>(<span style=\"color:#e6db74\">first_name<\/span>:, <span style=\"color:#e6db74\">last_name<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @members <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>new<span style=\"color:#f92672\">.<\/span>tap <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>user<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> first_name<\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>last_name <span style=\"color:#f92672\">=<\/span> last_name<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>username <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>band_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">_<\/span><span style=\"color:#e6db74\">#{<\/span>last_name<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> @members<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:last_name<\/span>)<span style=\"color:#f92672\">.<\/span>include?(last_name)<\/span><\/span><span style=\"display:flex;\"><span> user<span style=\"color:#f92672\">.<\/span>username <span style=\"color:#f92672\">&lt;&lt;<\/span> unique_value<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This works with any Ruby object, not just those that inherit from<code>ActiveRecord::Base<\/code>, so why bother with having to know if we can pass a blockto <code>new<\/code> or not, based on what the object inherits from?<\/p><p>That&rsquo;s fair, but <code>new<\/code> is not the only ActiveRecord method that takes a block.Others include <code>create<\/code>, <code>build<\/code>, and <code>find_or_initialize_by<\/code>. There thedifferences with <code>tap<\/code> start to show:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>new_user <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>tap <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>u<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> u<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Our <code>new_user<\/code> has the first name of Jonathan, resulting from the call to <code>tap<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>new_user<span style=\"color:#f92672\">.<\/span>first_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, that&rsquo;s only persisted in memory - not in the database. What we storedin the database is what we passed to <code>create<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>new_user<span style=\"color:#f92672\">.<\/span>reload<span style=\"color:#f92672\">.<\/span>first_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>We can also pass a block to <code>create<\/code> directly:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>new_user <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Jordan&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Knight&#34;<\/span>) <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>u<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> u<span style=\"color:#f92672\">.<\/span>first_name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>And in that case, the first name of the user in memory and in the database isJonathan.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>new_user<span style=\"color:#f92672\">.<\/span>first_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span>new_user<span style=\"color:#f92672\">.<\/span>reload<span style=\"color:#f92672\">.<\/span>first_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;Jonathan&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Why would we mix setting attributes with <code>create<\/code> both by passing a hash <em>and<\/em>a block, either with or without <code>tap<\/code>? Other than to explain quirks anddifferences in what method you&rsquo;re passing a block to, I am also interested inknowing. If you have real-world use cases, <a href=\"https:\/\/twitter.com\/kevin_j_m\">let me know<\/a>!<\/p><h2 id=\"finding-blocks-in-rails-source-code-face-the-musichttpsenwikipediaorgwikiface_the_music_new_kids_on_the_block_album\">Finding Blocks in Rails Source Code (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Face_the_Music_(New_Kids_on_the_Block_album)\">Face the Music<\/a>) <a href=\"#finding-blocks-in-rails-source-code-face-the-musichttpsenwikipediaorgwikiface_the_music_new_kids_on_the_block_album\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;re curious about where in Rails&rsquo; source code <code>new<\/code> is set up to take ablock, we can start by <a href=\"https:\/\/github.com\/rails\/rails\/blob\/70c5496542e5dc82ca28840cb01e710200ce5d14\/activerecord\/lib\/active_record\/base.rb#L299\">looking<\/a>in <code>ActiveRecord::Base<\/code>. As of the time this article was published, there&rsquo;s notmuch implementation in that class. Instead, we have to look in the <code>Core<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/70c5496542e5dc82ca28840cb01e710200ce5d14\/activerecord\/lib\/active_record\/core.rb#L582\">module<\/a>to find the <code>initialize<\/code> method that takes a block.<\/p><p>Initializing an ActiveRecord model with a block is also defined in the<a href=\"https:\/\/github.com\/rails\/rails\/blob\/70c5496542e5dc82ca28840cb01e710200ce5d14\/activerecord\/lib\/active_record\/base.rb#L35-L40\">documentation<\/a>.<\/p><p>Thanks for <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hangin%27_Tough\">hangin&rsquo; tough<\/a> tothe end of this article. I hope you learned a thing or two about passing blocksto ActiveRecord methods.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/activerecord-new-block\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Consistency Conundrum","link":"https:\/\/kevinjmurphy.com\/posts\/consistency-conundrum\/","pubDate":"Sat, 24 Apr 2021 14:14:09 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/consistency-conundrum\/","description":"<h2 id=\"evaluating-alternatives\">Evaluating Alternatives <a href=\"#evaluating-alternatives\">\ud83d\udd17<\/a><\/h2><ul><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/\">To Change Or Not To Change<\/a><\/li><li><strong>Consistency Conundrum<\/strong><\/li><\/ul><h2 id=\"consistent-starting-point\">Consistent Starting Point <a href=\"#consistent-starting-point\">\ud83d\udd17<\/a><\/h2><p>In our <a href=\"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/\">previous<\/a> post, we wrote the followingcode:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">complex_calculation<\/span>(base_price, promo_code)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># calculation<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We received this suggestion in code review:<\/p><blockquote><p>You could consider using keyword arguments for this method.<\/p><\/blockquote><p>On the whole, we agree that keyword arguments would benefit this method.However, if keyword arguments are never used in this application, does that meanwe shouldn&rsquo;t do it? We don&rsquo;t want to be inconsistent!<\/p><p>Let&rsquo;s consider some choices we could make specifically in regards to consistencywithin our application.<\/p><h2 id=\"options\">Options <a href=\"#options\">\ud83d\udd17<\/a><\/h2><h3 id=\"do-nothing\">Do Nothing <a href=\"#do-nothing\">\ud83d\udd17<\/a><\/h3><p>Generally, I don&rsquo;t think this gets the credit it deserves as anexplicitly-defined option. The inertia of the system may be too great to warrantmaking the change. The choice to write or structure code in the application thisway may be intentional and necessary. It may introduce too much cognitiveoverhead to justify making the change. There are lots of excuses I could make tojustify not doing the work!<\/p><p>I don&rsquo;t think any of these apply to our keyword arguments example. However, ifsomeone suggested:<\/p><blockquote><p>This computation is natively supported in <a href=\"https:\/\/numpy.org\/\">NumPy<\/a>. Haveyou considered integrating that?<\/p><\/blockquote><p>Then I may want to cross-reference the implementations for correctness, but Imay not want to rewrite my application in Python or explore Ruby\/Python bridgesto use that one function. I&rsquo;m drawing on the consistency of the rest of myapplication being written in Ruby to justify that choice.<\/p><h3 id=\"make-the-change-everywhere\">Make The Change Everywhere <a href=\"#make-the-change-everywhere\">\ud83d\udd17<\/a><\/h3><p>We could decide to invest the time and energy to use this convention<em>everywhere<\/em> immediately. Then, we&rsquo;d be consistent throughout our application!<\/p><p>In the case of our keyword arguments example, I would advise against that,unless there are only one or two other places where we&rsquo;d use them, and they&rsquo;reeasily identified.<\/p><p>Even then, I&rsquo;d think twice about it. Any change has a risk, and changing theworking code purely in the name of consistency is a risk with limited upside.<\/p><p>However, consider this change:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">desired_widgets<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Widget<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">&#34;status IN <\/span><span style=\"color:#e6db74\">#{<\/span>user_input<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>With the following code review suggestion:<\/p><blockquote><p>We should modify this method to sanitize user input before sending it as partof a database query.<\/p><\/blockquote><p>If we created the query like this because it&rsquo;s consistent with how otherqueries are written, then it&rsquo;s time to drop everything and change that everywhere.We&rsquo;ve opened ourselves up to <a href=\"https:\/\/owasp.org\/www-community\/attacks\/SQL_Injection\">SQL injection<\/a>attacks and we need to immediately invest the time to remediate those issues forour application&rsquo;s health and safety.<\/p><p>As a side note, consider a static security analysis tool like<a href=\"https:\/\/brakemanscanner.org\/\">Brakeman<\/a> to <a href=\"https:\/\/github.com\/TheGnarCo\/gnarails\/blob\/fe72e5fe74455400088d89f7af2a2d9bf1899d26\/templates\/bin\/brakeman\">run automatically<\/a>as part of your <a href=\"https:\/\/github.com\/TheGnarCo\/gnarails\/blob\/fe72e5fe74455400088d89f7af2a2d9bf1899d26\/templates\/.circleci\/config.yml#L49-L51\">build process<\/a>so that your application is not solely relying on reviewers&rsquo; eyes to catchcritical security implications.<\/p><h3 id=\"make-the-change-here-and-going-forward\">Make The Change Here, And Going Forward <a href=\"#make-the-change-here-and-going-forward\">\ud83d\udd17<\/a><\/h3><p>For our keyword arguments example, I suggested not to make the changeeverywhere. However, that doesn&rsquo;t mean we should ignore it in the name ofconsistency. If there&rsquo;s a demonstrated benefit, we should take advantage ofthat. Instead, we can embrace the consistency of the standard <em>going forward<\/em>.As you make future changes, be mindful of current best practices andconventions, and change existing code to meet those conventions as you haveanother reason to change the code.<\/p><p>Lastly, document these conventions so that it&rsquo;s clear to all team members whatthe expectations are going forward if you encounter this question. That canreduce ambiguity and help people understand why this internal inconsistency exists.<\/p><p>This approach of leaving the code better than you found it can applyto long-reaching goals as well. If you&rsquo;re converting an application from<a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/migrating-from-javascript.html\">JavaScript to TypeScript<\/a>,you can convert files as you need to change them. Ifyou&rsquo;re updating an application from <a href=\"https:\/\/relishapp.com\/rspec\/rspec-rails\/docs\/feature-specs\/feature-spec\">feature specs<\/a>to <a href=\"https:\/\/relishapp.com\/rspec\/rspec-rails\/docs\/system-specs\/system-spec\">system specs<\/a>,you can do that as you change the area of code the feature spec is testing.<\/p><h2 id=\"being-consistently-inconsistent\">Being Consistently Inconsistent <a href=\"#being-consistently-inconsistent\">\ud83d\udd17<\/a><\/h2><p>Software development is a rapidly evolving ecosystem of best practices, tools,and approaches. While we don&rsquo;t intend to chase trends, we also need toincorporate new improvements that will benefit our applications - as long aswe&rsquo;re intentional about them. For a healthy, long-running application, thatcan mean applying new paradigms in areas under active development, while leavingexisting work in its current state for the time being.<\/p><p>Living in the two worlds can be uncomfortable, and it is inconsistent. But it&rsquo;sintentional inconsistency with a plan towards consistency and based on a valuejudgement on the cost of the change vs. the benefit it&rsquo;ll provide.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/consistency-conundrum\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"To Change or Not to Change","link":"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/","pubDate":"Sun, 18 Apr 2021 07:29:25 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/to-change-or-not-to-change\/","description":"<h2 id=\"evaluating-alternatives\">Evaluating Alternatives <a href=\"#evaluating-alternatives\">\ud83d\udd17<\/a><\/h2><ul><li><strong>To Change Or Not To Change<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/consistency-conundrum\/\">Consistency Conundrum<\/a><\/li><\/ul><h2 id=\"optional-feedback\">Optional Feedback <a href=\"#optional-feedback\">\ud83d\udd17<\/a><\/h2><p>Imagine you&rsquo;ve just wrapped up a new feature and submitted it for code review,which includes the following method:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">complex_calculation<\/span>(base_price, promo_code)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># calculation<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>One of your team members reviews the change, and leaves a suggestion.<\/p><blockquote><p>You could consider using keyword arguments for this method.<\/p><\/blockquote><p>That would change the method to look like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">complex_calculation<\/span>(<span style=\"color:#e6db74\">base_price<\/span>:, <span style=\"color:#e6db74\">promo_code<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># calculation<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Do you make this change? How do you decide?<\/p><h2 id=\"evaluating-this-suggestion\">Evaluating This Suggestion <a href=\"#evaluating-this-suggestion\">\ud83d\udd17<\/a><\/h2><p>At <a href=\"https:\/\/railsconf.com\/2019\/program\/sessions#session-759\">RailsConf 2019<\/a>, Iasked the question, &ldquo;<a href=\"https:\/\/www.youtube.com\/watch?v=2NiePLJVjNI\">I know I can, but should I?<\/a>&rdquo;The talk provides a set of criteria you can use to make a choice betweenalternatives. Let&rsquo;s apply that here.<\/p><h3 id=\"impact\">Impact <a href=\"#impact\">\ud83d\udd17<\/a><\/h3><p>From a functionality perspective, this makes no perceived difference. The codewill work the same one way or another and neither have any known or noticeableimplications, such as performance.<\/p><p>However, this alternative arose from a reaction a team member had who also needsto work on this code, so there must be some value in this suggestion to them.Additionally, this change, one way or another, will end up serving as prior artthat may be used to base future decisions on.<\/p><p>The change itself has very limited impact on the code itself, but it does looklike there&rsquo;s a possible impact to the team worth exploring.<\/p><h3 id=\"cost\">Cost <a href=\"#cost\">\ud83d\udd17<\/a><\/h3><p>Any change involves risk - and risk has a cost. For example, here we can&rsquo;t onlymake the change to the method&rsquo;s signature. We also need to modify any callersto use the keyword arguments. If we miss one,and don&rsquo;t have complete test coverage, we may miss a call site - resulting in aruntime exception when the code is executed in production.<\/p><p>Considering that this is the place this method is introduced, this risk<em>should<\/em> be clear to mitigate. Any callers will be in this changeset, so weshould be able to identify them while reviewing this change. Making this changeitself is not particularly risky.<\/p><p>Changing this method to keyword arguments should not take much time - I&rsquo;vealready made the change to the method above! Now we only need to change thecallers. Even if this were an urgent need to get deployed as soon as possible,it seems reasonable that we could implement this here and now should we chooseto.<\/p><h3 id=\"maintenance\">Maintenance <a href=\"#maintenance\">\ud83d\udd17<\/a><\/h3><p>Using keyword arguments here provides two benefits to callers: readability andflexibility.<\/p><p>I&rsquo;m going to intentionally obscure variable names to make a point here, butcalling our method before looked like this:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">print_calculation_results<\/span><\/span><\/span><span style=\"display:flex;\"><span> calculator <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Calculator<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> result <span style=\"color:#f92672\">=<\/span> calculator<span style=\"color:#f92672\">.<\/span>complex_calculation(@x, @y)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> puts result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Compare this to the keyword args approach:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">print_calculation_results<\/span><\/span><\/span><span style=\"display:flex;\"><span> calculator <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Calculator<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> result <span style=\"color:#f92672\">=<\/span> complex_calculation(<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> <span style=\"color:#e6db74\">base_price<\/span>: @x,<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> <span style=\"color:#e6db74\">promo_code<\/span>: @y,<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> )<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> puts result<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>It&rsquo;s more verbose, but subjectively, I think it&rsquo;s also more readable andunderstandable.<\/p><p>It also frees us from caring about the position or order of the arguments.Calling the method this way works as well:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> result <span style=\"color:#f92672\">=<\/span> complex_calculation(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">promo_code<\/span>: @y,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">base_price<\/span>: @x,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><\/code><\/pre><\/div><p>I would tend to believe that the clarity and flexibility gained from the keywordarguments would make it easier to maintain code that used this method. It canalso prevent against subtle bugs, where you accidentally meant to pass <code>@x<\/code> as thebase price, but instead you passed <code>@y<\/code> first, because you forgot which argumentshould be in what order.<\/p><p>This seems like a maintenance win!<\/p><h3 id=\"consistency\">Consistency <a href=\"#consistency\">\ud83d\udd17<\/a><\/h3><p>Keyword arguments were added to the ruby langauge in <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2013\/02\/24\/ruby-2-0-0-p0-is-released\/\">Ruby 2.0.0<\/a>, originallyreleased in 2013. In my experience, its usage is not a surprise to mostrubyists, nor an unwelcome one. Even if someone is unfamiliar with them,understanding the basic usage of them is well-received.<\/p><p>We&rsquo;ll talk more about consistency at the end.<\/p><h2 id=\"verdict\">Verdict <a href=\"#verdict\">\ud83d\udd17<\/a><\/h2><p>We have an alternative that has no identified net impact on the function of thecode, but that a team member has a stated preference for. The change would notrequire an intense lift to accommodate, nor maintain. It (subjectively) improvesreadability and can prevent against subtle errors, and it uses a languagefeature that&rsquo;s been supported for many, many years.<\/p><p>In a vacuum, this suggestion seems like a net positive. Given our criteria here,we should accept this change!<\/p><p>Different changes will be evaluated against thecriteria differently, and the weighted importance of the criteria will change,but we can still use the rubric as a framework for how to make a decision.<\/p><p>For an in-depth explanation of these criteria, I recommend watching the <a href=\"https:\/\/www.youtube.com\/watch?v=2NiePLJVjNI&amp;feature=youtu.be\">fulltalk<\/a>.<\/p><h2 id=\"is-it-that-simple\">Is It That Simple? <a href=\"#is-it-that-simple\">\ud83d\udd17<\/a><\/h2><p>I said above that when considering this suggestion in isolation, it seems like anet benefit. However, this code does not exist in a vacuum. It&rsquo;s part of anexisting application, and needs to fit in with the rest of the ecosystem. Ileft out any discussion of consistency with the application itself.<\/p><p>If keyword arguments aren&rsquo;t used anywhere else in the application, should wereject the change because it&rsquo;s inconsistent? What criteria can we use todetermine how to maintain consistency <strong>and<\/strong> support change? We&rsquo;ll explore thatin our <a href=\"https:\/\/kevinjmurphy.com\/posts\/consistency-conundrum\/\">next post<\/a>.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/to-change-or-not-to-change\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Wrapping Up Rails Exceptional Behavior","link":"https:\/\/kevinjmurphy.com\/posts\/wrapping-up-rails-exceptional-behavior\/","pubDate":"Sun, 28 Mar 2021 14:15:20 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/wrapping-up-rails-exceptional-behavior\/","description":"<h2 id=\"exceptional-behavior-in-rails\">Exceptional Behavior in Rails <a href=\"#exceptional-behavior-in-rails\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/wrapping-about-exceptional-behavior-in-rails\/\">(W)rapping About Exceptional Behavior In Rails<\/a><\/li><li><strong>Wrapping Up Rails Exceptional Behavior<\/strong><\/li><\/ol><h2 id=\"reset\">Reset <a href=\"#reset\">\ud83d\udd17<\/a><\/h2><p>In our <a href=\"https:\/\/kevinjmurphy.com\/posts\/wrapping-about-exceptional-behavior-in-rails\/\">last post<\/a>, weencountered some inconsistent behavior between Rails 5 and Rails 6. In Rails 5,raising a <code>RuntimeError<\/code> in a controller after rescuing from an<code>ActiveRecord::RecordNotFound<\/code> exception was still returning a 404 HTTP statuscode. In Rails 6, the status code is a 500.<\/p><p>We looked around, and we think we&rsquo;ve isolated the area of interest to be in the<code>ExceptionWrapper<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/63d3f3f4d868a5ed9eacf00af2a80278aa005051\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb\">class<\/a>.<\/p><h2 id=\"revisit-the-wrapper\">Revisit The Wrapper <a href=\"#revisit-the-wrapper\">\ud83d\udd17<\/a><\/h2><p>We looked into what was creating our wrapper and discovered that we were alwayspassing it the <code>RuntimeError<\/code>. After taking a much-needed break, we startreading the code again, and, almost immediately, we see a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/63d3f3f4d868a5ed9eacf00af2a80278aa005051\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L36\">transformation<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(backtrace_cleaner, exception)<\/span><\/span><span style=\"display:flex;\"><span> @backtrace_cleaner <span style=\"color:#f92672\">=<\/span> backtrace_cleaner<\/span><\/span><span style=\"display:flex;\"><span> @exception <span style=\"color:#f92672\">=<\/span> original_exception(exception)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The exception that is passed in is modified. Let&rsquo;s look at this<code>original_exception<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/63d3f3f4d868a5ed9eacf00af2a80278aa005051\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L106-L112\">method<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">original_exception<\/span>(exception)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> @@rescue_responses<span style=\"color:#f92672\">.<\/span>has_key?(exception<span style=\"color:#f92672\">.<\/span>cause<span style=\"color:#f92672\">.<\/span>class<span style=\"color:#f92672\">.<\/span>name)<\/span><\/span><span style=\"display:flex;\"><span> exception<span style=\"color:#f92672\">.<\/span>cause<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">else<\/span><\/span><\/span><span style=\"display:flex;\"><span> exception<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Recall that our <code>RuntimeError<\/code> is raised as a result of handling an<code>ActiveRecord::RecordNotFound<\/code> exception. The <code>RecordNotFound<\/code> exception <strong>is<\/strong> the<a href=\"https:\/\/ruby-doc.org\/core-3.0.0\/Exception.html#method-i-cause\">cause<\/a> of the <code>RuntimeError<\/code>. We previously discovered that <code>RecordNotFound<\/code> is added to <code>@@rescue_responses<\/code> in ActiveRecord&rsquo;s <a href=\"https:\/\/github.com\/rails\/rails\/blob\/d75c2a175215c0f6d011b60f1c9f2b6466184adb\/activerecord\/lib\/active_record\/railtie.rb#L22-L27\">railtie<\/a>.<\/p><p>The cause of our exception is in the hash, and as such, the <strong>cause<\/strong> is set asthe <code>@exception<\/code> variable in the initializer. That cause is <code>RecordNotFound<\/code>,and a <code>RecordNotFound<\/code> exception is supposed to return a 404 status code.<\/p><p>We can now explain why a 404 is returned!<\/p><h2 id=\"regifting-rails-6-redux\">Regifting (Rails 6 Redux) <a href=\"#regifting-rails-6-redux\">\ud83d\udd17<\/a><\/h2><p>We now have a handle on the behavior in Rails 5; however, this investigationstarted because we noticed it was different in Rails 5 and Rails 6. Let&rsquo;s checkin on the <code>ExceptionWrapper<\/code> initializer in <a href=\"https:\/\/github.com\/rails\/rails\/blob\/0440369d03ae99f9f044b00e39dcd3d9871c65c2\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L46-L48\">Rails 6<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(backtrace_cleaner, exception)<\/span><\/span><span style=\"display:flex;\"><span> @backtrace_cleaner <span style=\"color:#f92672\">=<\/span> backtrace_cleaner<\/span><\/span><span style=\"display:flex;\"><span> @exception <span style=\"color:#f92672\">=<\/span> exception<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>No longer are we retrieving the <code>original_exception<\/code>. That doesn&rsquo;t tell thewhole story though. When we ask for the <a href=\"https:\/\/github.com\/rails\/rails\/blob\/4c78cc8b04861f02d660aefc37979eb2244db6ba\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L62\">status code<\/a>, we&rsquo;re not using <code>@exception<\/code>. Instead, we now have an <code>unwrapped_exception<\/code> to <a href=\"https:\/\/github.com\/rails\/rails\/blob\/4c78cc8b04861f02d660aefc37979eb2244db6ba\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L49-L55\">investigate<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">unwrapped_exception<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> wrapper_exceptions<span style=\"color:#f92672\">.<\/span>include?(exception<span style=\"color:#f92672\">.<\/span>class<span style=\"color:#f92672\">.<\/span>to_s)<\/span><\/span><span style=\"display:flex;\"><span> exception<span style=\"color:#f92672\">.<\/span>cause<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">else<\/span><\/span><\/span><span style=\"display:flex;\"><span> exception<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Rather than looking in <code>rescue_responses<\/code>, we&rsquo;re now looking in<code>wrapper_exceptions<\/code>, which it appears is a <a href=\"https:\/\/github.com\/rails\/rails\/blob\/4c78cc8b04861f02d660aefc37979eb2244db6ba\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L35-L37\">list<\/a> of one exception that shouldbehave particularly exceptionally.<\/p><p>If the exception is an <code>ActionView::Template::Error<\/code>, then look up the statuscode based on the cause of the exception. Otherwise, determine it based on theexception itself.<\/p><p><code>RuntimeError<\/code> isn&rsquo;t in this list of <code>wrapper_exceptions<\/code>, so we don&rsquo;t use thecause (<code>ActiveRecord::RecordNotFound<\/code>) to determine the status code. We use the<code>RuntimeError<\/code> itself. That has no special handling in <code>rescue_responses<\/code>, so a500 HTTP status code is returned.<\/p><h2 id=\"thank-you-card\">Thank You Card <a href=\"#thank-you-card\">\ud83d\udd17<\/a><\/h2><p>The <a href=\"https:\/\/github.com\/rails\/rails\/pull\/35049\/commits\/ef40fb6fd88f2e3c3f989aef65e3ddddfadee814\">commit<\/a> that makes this change contains a very well-worded description of this scenario, including:<\/p><blockquote><p>When the cause is mapped to an HTTP status code the last exception is unexpectedly uwrapped<\/p><\/blockquote><p>Thanks to <a href=\"https:\/\/github.com\/yuki24\">Yuki Nishijima<\/a> for fixing this!<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/wrapping-up-rails-exceptional-behavior\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"(W)rapping About Exceptional Behavior in Rails","link":"https:\/\/kevinjmurphy.com\/posts\/wrapping-about-exceptional-behavior-in-rails\/","pubDate":"Sun, 21 Mar 2021 20:24:01 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/wrapping-about-exceptional-behavior-in-rails\/","description":"<h2 id=\"exceptional-behavior-in-rails\">Exceptional Behavior in Rails <a href=\"#exceptional-behavior-in-rails\">\ud83d\udd17<\/a><\/h2><ol><li><strong>(W)rapping About Exceptional Behavior In Rails<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/wrapping-up-rails-exceptional-behavior\/\">Wrapping Up Rails Exceptional Behavior<\/a><\/li><\/ol><h2 id=\"reset\">Reset <a href=\"#reset\">\ud83d\udd17<\/a><\/h2><h2 id=\"pop-quiz\">Pop Quiz <a href=\"#pop-quiz\">\ud83d\udd17<\/a><\/h2><p>Consider the following controller:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FoosController<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">ActionController<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">API<\/span><\/span><\/span><span style=\"display:flex;\"><span> rescue_from <span style=\"color:#66d9ef\">ActiveRecord<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">RecordNotFound<\/span>, <span style=\"color:#e6db74\">with<\/span>: <span style=\"color:#e6db74\">:raise_not_found<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">create<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#66d9ef\">ActiveRecord<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">RecordNotFound<\/span><\/span><\/span><span style=\"display:flex;\"><span> head <span style=\"color:#e6db74\">:ok<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">raise_not_found<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">raise<\/span> <span style=\"color:#e6db74\">&#34;What status code?&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Here we have an endpoint that that always raises <code>ActiveRecord::RecordNotFound<\/code>.However, we&rsquo;re <a href=\"https:\/\/api.rubyonrails.org\/v6.1.1\/classes\/ActiveSupport\/Rescuable\/ClassMethods.html\">using<\/a><code>rescue_from<\/code> to handle this exception, and raise a new <code>RuntimeError<\/code>.<\/p><p>When you hit this controller endpoint, what status code do you expect toreceive?<\/p><h2 id=\"it-depends\">It Depends <a href=\"#it-depends\">\ud83d\udd17<\/a><\/h2><p>In Rails 6, we get a 500.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>&gt; curl -I -X POST localhost:3000\/foos<\/span><\/span><span style=\"display:flex;\"><span>HTTP\/1.1 <span style=\"color:#ae81ff\">500<\/span> Internal Server Error<\/span><\/span><\/code><\/pre><\/div><p>However, in Rails 5, we get a 404.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>&gt; curl -I -X POST localhost:3000\/foos<\/span><\/span><span style=\"display:flex;\"><span>HTTP\/1.1 <span style=\"color:#ae81ff\">404<\/span> Not Found<\/span><\/span><\/code><\/pre><\/div><p>Why does this difference exist? Let&rsquo;s dig into how to investigate.<\/p><h2 id=\"research\">Research <a href=\"#research\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;ve ever hit the <code>#show<\/code> route for a resource, passing it an ID that doesn&rsquo;texist, you may have noticed that Rails responds with a 404 Not Found HTTP statuscode. Rails is intercepting the exception that&rsquo;s raised from calling <code>find<\/code>(<code>ActiveRecord::RecordNotFound<\/code>) and, rather than returning a 500, it knows toreturn a 404 instead.<\/p><h3 id=\"start-with-what-you-know\">Start With What You Know <a href=\"#start-with-what-you-know\">\ud83d\udd17<\/a><\/h3><p>To give us some grounding, let&rsquo;s begin by explicitly stating what we believe to betrue:<\/p><ul><li>Rails has some special handling of certain exceptions to respond with adifferent HTTP status code.<\/li><li>If we <code>rescue_from<\/code> one of those exceptions in Rails 5 and re-raise a newexception, we&rsquo;ll still see that special handling behavior.<\/li><li>In Rails 6, with the same implementation, the server now responds with a 500,no longer considering it a special case.<\/li><\/ul><h3 id=\"go-explore\">Go Explore <a href=\"#go-explore\">\ud83d\udd17<\/a><\/h3><p>The exception that we know exhibits this behavior is<code>ActiveRecord::RecordNotFound<\/code>. We also know that Rails returns a 404 Not Foundstatus code. If we search through the Rails codebase for <a href=\"https:\/\/github.com\/rails\/rails\/search?p=1&amp;q=RecordNotFound\">RecordNotFound<\/a> or <a href=\"https:\/\/github.com\/rails\/rails\/search?p=1&amp;q=not_found\">not_found<\/a> (which is the <a href=\"https:\/\/guides.rubyonrails.org\/layouts_and_rendering.html#using-render\">symbol representation<\/a> of the status code), we get a number of results - but there are fewer for <code>not_found<\/code>, so let&rsquo;s look through that.<\/p><p>Towards the bottom of the first page of results (as of this writing), we see the<code>ExceptionWrapper<\/code> class, which includes <a href=\"https:\/\/github.com\/rails\/rails\/blob\/291a3d2ef29a3842d1156ada7526f4ee60dd2b59\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L8-L24\">this<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span> cattr_accessor <span style=\"color:#e6db74\">:rescue_responses<\/span>, <span style=\"color:#e6db74\">default<\/span>: <span style=\"color:#66d9ef\">Hash<\/span><span style=\"color:#f92672\">.<\/span>new(<span style=\"color:#e6db74\">:internal_server_error<\/span>)<span style=\"color:#f92672\">.<\/span>merge!(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::RoutingError&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_found<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;AbstractController::ActionNotFound&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_found<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::MethodNotAllowed&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:method_not_allowed<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::UnknownHttpMethod&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:method_not_allowed<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::NotImplemented&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_implemented<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::UnknownFormat&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_acceptable<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionDispatch::Http::MimeNegotiation::InvalidType&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_acceptable<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::MissingExactTemplate&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_acceptable<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::InvalidAuthenticityToken&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:unprocessable_entity<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::InvalidCrossOriginRequest&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:unprocessable_entity<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionDispatch::Http::Parameters::ParseError&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:bad_request<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::BadRequest&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:bad_request<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActionController::ParameterMissing&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:bad_request<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Rack::QueryParser::ParameterTypeError&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:bad_request<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;Rack::QueryParser::InvalidParameterError&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:bad_request<\/span><\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><\/code><\/pre><\/div><p>This looks like a big list of exceptions that are mapped to HTTP status codes.This seems promising - but <code>ActiveRecord::RecordNotFound<\/code> isn&rsquo;t in this hash!<\/p><p>If we search further, we will eventually run into <a href=\"https:\/\/github.com\/rails\/rails\/blob\/d75c2a175215c0f6d011b60f1c9f2b6466184adb\/activerecord\/lib\/active_record\/railtie.rb#L22-L27\">ActiveRecord&rsquo;s railtie<\/a>,which includes:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>config<span style=\"color:#f92672\">.<\/span>action_dispatch<span style=\"color:#f92672\">.<\/span>rescue_responses<span style=\"color:#f92672\">.<\/span>merge!(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActiveRecord::RecordNotFound&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:not_found<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActiveRecord::StaleObjectError&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:conflict<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActiveRecord::RecordInvalid&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:unprocessable_entity<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;ActiveRecord::RecordNotSaved&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">:unprocessable_entity<\/span><\/span><\/span><span style=\"display:flex;\"><span>)<\/span><\/span><\/code><\/pre><\/div><p>This is adding ActiveRecord exceptions to that same hash we saw above in<code>ActionDispatch<\/code>. We&rsquo;ve found where the special handling of mapping exceptionsto status codes occurs!<\/p><h3 id=\"pull-out-some-tools\">Pull Out Some Tools <a href=\"#pull-out-some-tools\">\ud83d\udd17<\/a><\/h3><p>Returning to the <code>ExceptionWrapper<\/code> class, we see the <code>status_code_for_exception<\/code> <a href=\"https:\/\/github.com\/rails\/rails\/blob\/291a3d2ef29a3842d1156ada7526f4ee60dd2b59\/actionpack\/lib\/action_dispatch\/middleware\/exception_wrapper.rb#L117-L119\">method<\/a>, which looks to take in an exception class and convert it to a status code, based on the <code>@@rescue_responses<\/code> hash.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">status_code_for_exception<\/span>(class_name)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rack<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Utils<\/span><span style=\"color:#f92672\">.<\/span>status_code(@@rescue_responses<span style=\"color:#f92672\">[<\/span>class_name<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Maybe if we could see what class name is getting passed into that, we could seeif the exception is somehow getting transformed before or after that point. But,we need a way to get into the rails source code on our running rails app -that&rsquo;s not our code; how can we do that?<\/p><h4 id=\"opening-a-world-of-possibilities\"><code>Open<\/code>ing A World of Possibilities <a href=\"#opening-a-world-of-possibilities\">\ud83d\udd17<\/a><\/h4><p>Rails 5 appears to be showing confusing results - we&rsquo;re explicitly raising a<code>RuntimeError<\/code>, but it&rsquo;s returning a 404. As such, let&rsquo;s look in Rails 5.<\/p><p>You can <a href=\"https:\/\/boringrails.com\/tips\/bundle-open-debug-gems\">access the source<\/a>of any dependency using bundler&rsquo;s <code>open<\/code> <a href=\"https:\/\/bundler.io\/bundle_open.html\">command<\/a>.Rails itself is a series of gems, and we can see that this <code>ExceptionWrapper<\/code>is part of <code>actionpack<\/code>, so let&rsquo;s open that up:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>bundle open actionpack<\/span><\/span><\/code><\/pre><\/div><p>This will open up the source code for the <code>actionpack<\/code> gem in the editor you havedefined. We open the <code>ExceptionWrapper<\/code> class, and we know we want to find outwhat value is passed to it, but we&rsquo;re not sure what else we might want to seewhile we&rsquo;re there. Using <a href=\"https:\/\/ruby-doc.org\/stdlib-3.0.0\/libdoc\/irb\/rdoc\/Binding.html\">Ruby&rsquo;s<\/a> <code>binding.irb<\/code>, we can <a href=\"https:\/\/jemma.dev\/blog\/binding-irb\">start a console<\/a> when we hit that method.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">status_code_for_exception<\/span>(class_name)<\/span><\/span><span style=\"display:flex;\"><span> binding<span style=\"color:#f92672\">.<\/span>irb<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rack<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Utils<\/span><span style=\"color:#f92672\">.<\/span>status_code(@@rescue_responses<span style=\"color:#f92672\">[<\/span>class_name<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>After starting a rails server, and issuing a request to our endpoint via curl,we eventually hit our breakpoint:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">From<\/span>: <span style=\"color:#e6db74\">\/ruby\/<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">\/<\/span>lib<span style=\"color:#f92672\">\/<\/span>ruby<span style=\"color:#f92672\">\/<\/span>gems<span style=\"color:#f92672\">\/<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">\/<\/span>gems<span style=\"color:#f92672\">\/<\/span>actionpack<span style=\"color:#f92672\">-<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">4<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">\/<\/span>lib<span style=\"color:#f92672\">\/<\/span>action_dispatch<span style=\"color:#f92672\">\/<\/span>middleware<span style=\"color:#f92672\">\/<\/span>exception_wrapper<span style=\"color:#f92672\">.<\/span>rb <span style=\"color:#960050;background-color:#1e0010\">@<\/span> line <span style=\"color:#ae81ff\">86<\/span> :<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">81<\/span>: <span style=\"color:#e6db74\">&#34;Full Trace&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> full_trace_with_ids<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">82<\/span>: }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">83<\/span>: <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">84<\/span>:<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">85<\/span>: <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">status_code_for_exception<\/span>(class_name)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">86<\/span>: binding<span style=\"color:#f92672\">.<\/span>irb<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">87<\/span>: <span style=\"color:#66d9ef\">Rack<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Utils<\/span><span style=\"color:#f92672\">.<\/span>status_code(@@rescue_responses<span style=\"color:#f92672\">[<\/span>class_name<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">88<\/span>: <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">89<\/span>:<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">90<\/span>: <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">source_extracts<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">91<\/span>: backtrace<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>trace<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>irb(<span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ExceptionWrapper<\/span>):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> class_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;RuntimeError&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>This isn&rsquo;t surprising that we&rsquo;re getting the <code>RuntimeError<\/code>, but doesn&rsquo;t helpexplain how we&rsquo;re getting a 404 returned. Let&rsquo;s exit and regroup on a newstrategy.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(<span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ExceptionWrapper<\/span>):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> exit<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Completed<\/span> <span style=\"color:#ae81ff\">500<\/span> <span style=\"color:#66d9ef\">Internal<\/span> <span style=\"color:#66d9ef\">Server<\/span> <span style=\"color:#66d9ef\">Error<\/span> <span style=\"color:#66d9ef\">in<\/span> <span style=\"color:#ae81ff\">2<\/span>ms (<span style=\"color:#e6db74\">ActiveRecord<\/span>: <span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">0<\/span>ms)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">From<\/span>: <span style=\"color:#e6db74\">\/ruby\/<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">\/<\/span>lib<span style=\"color:#f92672\">\/<\/span>ruby<span style=\"color:#f92672\">\/<\/span>gems<span style=\"color:#f92672\">\/<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">6<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">\/<\/span>gems<span style=\"color:#f92672\">\/<\/span>actionpack<span style=\"color:#f92672\">-<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">4<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">\/<\/span>lib<span style=\"color:#f92672\">\/<\/span>action_dispatch<span style=\"color:#f92672\">\/<\/span>middleware<span style=\"color:#f92672\">\/<\/span>exception_wrapper<span style=\"color:#f92672\">.<\/span>rb <span style=\"color:#960050;background-color:#1e0010\">@<\/span> line <span style=\"color:#ae81ff\">86<\/span> :<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">81<\/span>: <span style=\"color:#e6db74\">&#34;Full Trace&#34;<\/span> <span style=\"color:#f92672\">=&gt;<\/span> full_trace_with_ids<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">82<\/span>: }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">83<\/span>: <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">84<\/span>:<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">85<\/span>: <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">self<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#a6e22e\">status_code_for_exception<\/span>(class_name)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">86<\/span>: binding<span style=\"color:#f92672\">.<\/span>irb<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">87<\/span>: <span style=\"color:#66d9ef\">Rack<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Utils<\/span><span style=\"color:#f92672\">.<\/span>status_code(@@rescue_responses<span style=\"color:#f92672\">[<\/span>class_name<span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">88<\/span>: <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">89<\/span>:<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">90<\/span>: <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">source_extracts<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">91<\/span>: backtrace<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>trace<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>irb(<span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ExceptionWrapper<\/span>):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span><\/span><\/span><\/code><\/pre><\/div><p>Wait! This method is called a second time. And that time, the class name is<em>still<\/em> <code>&quot;RuntimeError&quot;<\/code>. It gets called a <strong>third<\/strong> time, and that time, we see:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(<span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ExceptionWrapper<\/span>):<span style=\"color:#ae81ff\">001<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> class_name<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#e6db74\">&#34;ActiveRecord::RecordNotFound&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ve found our <code>NotFound<\/code>! But - what do we do now?<\/p><h3 id=\"calling-in-a-favor\"><code>Call<\/code>ing In A Favor <a href=\"#calling-in-a-favor\">\ud83d\udd17<\/a><\/h3><p>We&rsquo;re still in our console session, and we&rsquo;ve executed the <code>status_code_for_exception<\/code> for the third time when processing a single HTTP request. What&rsquo;s calling this method? Ruby will tell us its <a href=\"https:\/\/ruby-doc.org\/core-3.0.0\/Kernel.html#method-i-caller\">caller<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>irb(<span style=\"color:#66d9ef\">ActionDispatch<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">ExceptionWrapper<\/span>):<span style=\"color:#ae81ff\">002<\/span>:<span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">&gt;<\/span> caller<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/workspace.rb:85:in `eval&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/workspace.rb:85:in `evaluate&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/context.rb:385:in `evaluate&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:493:in `block (2 levels) in eval_input&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:647:in `signal_status&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:490:in `block in eval_input&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/ruby-lex.rb:232:in `loop&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/ruby-lex.rb:232:in `block in each_top_level_statement&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/ruby-lex.rb:231:in `catch&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb\/ruby-lex.rb:231:in `each_top_level_statement&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:489:in `eval_input&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:428:in `block in run&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:427:in `catch&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:427:in `run&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/2.6.0\/irb.rb:796:in `irb&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/exception_wrapper.rb:86:in `status_code_for_exception&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/exception_wrapper.rb:46:in `status_code&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/debug_exceptions.rb:105:in `render_for_browser_request&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/debug_exceptions.rb:87:in `render_exception&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/web-console-3.7.0\/lib\/web_console\/extensions.rb:28:in `render_exception_with_web_console&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/debug_exceptions.rb:71:in `rescue in call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/debug_exceptions.rb:59:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/web-console-3.7.0\/lib\/web_console\/middleware.rb:135:in `call_app&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/web-console-3.7.0\/lib\/web_console\/middleware.rb:30:in `block in call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/web-console-3.7.0\/lib\/web_console\/middleware.rb:20:in `catch&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/web-console-3.7.0\/lib\/web_console\/middleware.rb:20:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/show_exceptions.rb:33:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/railties-5.2.4.5\/lib\/rails\/rack\/logger.rb:38:in `call_app&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/railties-5.2.4.5\/lib\/rails\/rack\/logger.rb:26:in `block in call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/activesupport-5.2.4.5\/lib\/active_support\/tagged_logging.rb:71:in `block in tagged&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/activesupport-5.2.4.5\/lib\/active_support\/tagged_logging.rb:28:in `tagged&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/activesupport-5.2.4.5\/lib\/active_support\/tagged_logging.rb:71:in `tagged&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/railties-5.2.4.5\/lib\/rails\/rack\/logger.rb:26:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/sprockets-rails-3.2.2\/lib\/sprockets\/rails\/quiet_assets.rb:13:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/remote_ip.rb:81:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/request_id.rb:27:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/rack-2.2.3\/lib\/rack\/method_override.rb:24:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/rack-2.2.3\/lib\/rack\/runtime.rb:22:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/activesupport-5.2.4.5\/lib\/active_support\/cache\/strategy\/local_cache_middleware.rb:29:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/executor.rb:14:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/actionpack-5.2.4.5\/lib\/action_dispatch\/middleware\/static.rb:127:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/rack-2.2.3\/lib\/rack\/sendfile.rb:110:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/railties-5.2.4.5\/lib\/rails\/engine.rb:524:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/puma-3.12.6\/lib\/puma\/configuration.rb:227:in `call&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/puma-3.12.6\/lib\/puma\/server.rb:706:in `handle_request&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/puma-3.12.6\/lib\/puma\/server.rb:476:in `process_client&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/puma-3.12.6\/lib\/puma\/server.rb:334:in `block in run&#39;&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#e6db74\">&#34;\/ruby\/2.6.6\/lib\/ruby\/gems\/2.6.0\/gems\/puma-3.12.6\/lib\/puma\/thread_pool.rb:135:in `block in spawn_thread&#39;&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Starting from the bottom, we can see the full path that leads us to executing<code>status_code_for_exception<\/code>. Tracing our way backwards from<code>status_code_for_exception<\/code>, we eventually find where the <code>ExceptionWrapper<\/code> is<a href=\"https:\/\/github.com\/rails\/rails\/blob\/63d3f3f4d868a5ed9eacf00af2a80278aa005051\/actionpack\/lib\/action_dispatch\/middleware\/debug_exceptions.rb#L78\">created<\/a>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">render_exception<\/span>(request, exception)<\/span><\/span><span style=\"display:flex;\"><span> backtrace_cleaner <span style=\"color:#f92672\">=<\/span> request<span style=\"color:#f92672\">.<\/span>get_header(<span style=\"color:#e6db74\">&#34;action_dispatch.backtrace_cleaner&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> wrapper <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">ExceptionWrapper<\/span><span style=\"color:#f92672\">.<\/span>new(backtrace_cleaner, exception)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><\/code><\/pre><\/div><p>We drop a <code>binding.irb<\/code> in here, and it looks like the exception is always our<code>RuntimeError<\/code>. It looks like something inside of <code>ExceptionWrapper<\/code> is doing<em>something<\/em> to change this to an <code>ActiveRecord::RecordNotFound<\/code> exception.<\/p><h3 id=\"walk-away\">Walk Away <a href=\"#walk-away\">\ud83d\udd17<\/a><\/h3><p>We&rsquo;ve been at this for a while, and we&rsquo;ve got a lead to track down, but at thispoint, let&rsquo;s take a break. We should clean up our mess by <a href=\"https:\/\/bundler.io\/man\/bundle-pristine.1.html\">restoring<\/a><code>actionpack<\/code> back to how it was before.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>bundle pristine<\/span><\/span><\/code><\/pre><\/div><p>Let&rsquo;s take stock in what we&rsquo;ve done:<\/p><ul><li>Identified that Rails has a list of exceptions that it maps to specific HTTPstatus codes.<\/li><li>Used <code>bundle open<\/code> to manipulate the source code of our dependency.<\/li><li>Inserted a <code>binding.irb<\/code> to play around in a method that we think isinteresting, and asked where it&rsquo;s being invoked with <code>caller<\/code>.<\/li><li>Confirmed that what&rsquo;s calling our class in question is always passing it a <code>RuntimeError<\/code>.<\/li><\/ul><p>We still don&rsquo;t know where the <code>ActiveRecord::RecordNotFound<\/code> exception is comingfrom.<\/p><p>We&rsquo;ll take another look at this with fresh eyes in our <a href=\"https:\/\/kevinjmurphy.com\/posts\/wrapping-up-rails-exceptional-behavior\/\">next post<\/a>.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/wrapping-about-exceptional-behavior-in-rails\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Engineering MBA","link":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/","pubDate":"Fri, 12 Mar 2021 20:30:07 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba\/","description":"<h2 id=\"apply-to-graduate\">Apply To Graduate <a href=\"#apply-to-graduate\">\ud83d\udd17<\/a><\/h2><p>Congratulations on completing your coursework. To receive your diploma, pleasefill out the form below.<\/p><p style=\"text-align: center;\"> <a href=\"https:\/\/engineering-mba-diploma.herokuapp.com\/\")>Claim Your Diploma<\/a><\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/6ldCGo29w5g\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/youtu.be\/6ldCGo29w5g\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/engineering-mba\">Slides<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-proposal\/\">Proposal<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/situational-leadership\/\">Situational Leadership<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/competitive-advantage\/\">Competitive Advantage<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/core-competency\/\">Core Competency<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/business-process-management\/\">Business Process Management<\/a><\/li><\/ul><h2 id=\"curriculum\">Curriculum <a href=\"#curriculum\">\ud83d\udd17<\/a><\/h2><h3 id=\"situational-leadership\">Situational Leadership <a href=\"#situational-leadership\">\ud83d\udd17<\/a><\/h3><p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Situational_leadership_theory\">Situational Leadership Model<\/a> posits that there are four different styles ofleadership.<\/p><ul><li>Directing - provide close, detailed supervision with clear instructions andexpectations.<\/li><li>Coaching - focus on getting buy-in from the team on explaining why decisionsare made the way they are.<\/li><li>Supporting - shift decision-making ownership to the team and facilitatediscussion amongst the team to reach decisions and provide encouragement.<\/li><li>Delegating - focus on overall vision, trusting the team with autonomy onday-to-day decision-making.<\/li><\/ul><p>A leader must be able to exhibit all of these styles. Otherwise, they&rsquo;ll only beeffective in very specific scenarios with a very specific group of people at agiven time. In order to determine which style to use at a given time, considerhow much relationship and task behavior is required by the individual or team atthe moment.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/situational-leadership.png\"><\/figure><p>Consider this model when you&rsquo;re in a position of leadership, or to identify whattype of leadership you or your team would benefit from at the moment. Sprintplanning exercises are an opportunity to understand how different leadershipstyles can benefit different people on a specific task.<\/p><h3 id=\"competitive-advantage\">Competitive Advantage <a href=\"#competitive-advantage\">\ud83d\udd17<\/a><\/h3><p>A firm&rsquo;s <a href=\"https:\/\/corporatefinanceinstitute.com\/resources\/knowledge\/strategy\/competitive-advantage\/\">competitive advantage<\/a> is the attributes that allow it to flourish against competitors. There are three general competitive <a href=\"https:\/\/www.ifm.eng.cam.ac.uk\/research\/dstools\/porters-generic-competitive-strategies\/\">strategies<\/a>:<\/p><ul><li>Cost.<\/li><li>Differentiation.<\/li><li>Focus.<\/li><\/ul><h4 id=\"core-competency\">Core Competency <a href=\"#core-competency\">\ud83d\udd17<\/a><\/h4><p>A firm&rsquo;s <a href=\"https:\/\/link.springer.com\/chapter\/10.1007\/978-3-662-41482-8_46\">core competencies<\/a> are the very specific set of qualities or characteristics that drive success. A core competency must meet all of the following criteria:<\/p><ul><li>Provides access to various markets.<\/li><li>Contributes to perceived customer benefit.<\/li><li>Difficult for competitors to imitate.<\/li><\/ul><p>Identification of core competencies can aid in more quickly answering your nextbuild vs. buy vs. borrow conundrum when building out a feature.<\/p><h3 id=\"business-process-management\">Business Process Management <a href=\"#business-process-management\">\ud83d\udd17<\/a><\/h3><p>Dr. Thomas Davenport <a href=\"https:\/\/books.google.com\/books?id=kLlIOMGaKnsC&amp;lpg=PP1&amp;pg=PA5#v=onepage&amp;q&amp;f=false\">defines a process<\/a> as:<\/p><blockquote><p>a structured, measured set of activities designed to produce a specificoutput.<\/p><\/blockquote><p>The Association of Business Process Management <a href=\"https:\/\/www.abpmp.org\/page\/BPM_Profession\">states<\/a>that Business Process Management<\/p><blockquote><p>enables an enterprise to align its business processes to its businessstrategy.<\/p><\/blockquote><p>Managing and delivering iterative improvements of a <a href=\"https:\/\/www.bpminstitute.org\/resources\/articles\/what-bpm-anyway-business-process-management-explained\">business process<\/a> involvesfollowing these cyclical steps:<\/p><ul><li>Design - document the existing process and propose an improved future state.<\/li><li>Model - perform &ldquo;what if&rdquo; analysis to subject different theoreticalconstraints to the proposed future state.<\/li><li>Execute - in an automated <em>or<\/em> manual fashion, roll out the future state to asmall group.<\/li><li>Monitor - track and report on key performance indicators (KPIs) to observethe impact of the change.<\/li><li>Optimize - identify current bottlenecks and feed that in to future designiterations.<\/li><\/ul><h4 id=\"business-process-reengineering\">Business Process Reengineering <a href=\"#business-process-reengineering\">\ud83d\udd17<\/a><\/h4><p><a href=\"https:\/\/hbr.org\/1990\/07\/reengineering-work-dont-automate-obliterate\">Reengineering<\/a> is the big rewrite of processes. The process is reimagined from the ground-up, ensuring that each step in the flow directly contributes to the intended goal and adds value.<\/p><p>Much of our work as software developers involves managing processes. Whetherwe&rsquo;re optimizing our individual developer workflow, or automating businessprocesses, we can follow the same methodical approach to know if we&rsquo;ve succeededin improving stakeholders&rsquo; well-being.<\/p><p>Ensure that the ultimate focus is notpurely on solving the optimization problem - consider the impact andconsequences of the work being delivered for users of the system and thoseotherwise impacted by the decisions made by the system.<\/p><p>Dr. Hammer, credited with introducing the concept of Business ProcessReengineering, later <a href=\"https:\/\/www.wsj.com\/articles\/SB848961197738163500\">stated<\/a> that,<\/p><blockquote><p>I was reflecting my engineering background and was insufficientlyappreciative of the human dimension.<\/p><\/blockquote><p>Dr. Davenport, who provided our definition of a process above, also<a href=\"https:\/\/www.fastcompany.com\/26310\/fad-forgot-people\">remarked<\/a>,<\/p><blockquote><p>The rock that reengineering has foundered on is simple: people.<\/p><\/blockquote><p>Consider the consequences of our work. Not only for the team building thesystem, or using the system, or that uses its output. Consider the impact onsystems and individuals who are affected by the decisions your system makes whodon&rsquo;t even know your system exists.<\/p><h2 id=\"extra-credit-reading\">Extra Credit Reading <a href=\"#extra-credit-reading\">\ud83d\udd17<\/a><\/h2><ul><li><a href=\"https:\/\/www.amazon.com\/Getting-Yes-Negotiating-Agreement-Without\/dp\/0143118757\">Getting To Yes<\/a>: This is a seminal read on negotiation tactics. If you&rsquo;ve heard of BATNA, best alternative to a negotiated agreement, this is where that comes from.<\/li><li><a href=\"https:\/\/www.amazon.com\/Reengineering-Corporation-Manifesto-Business-Revolution\/dp\/0887306403\">Reengineering the Corporation<\/a>: Our coursework introduces the topic of Business Process Reengineering. If you&rsquo;d like to dig in further, this is the book to pick up.<\/li><li><a href=\"https:\/\/www.amazon.com\/Blue-Ocean-Strategy-Expanded-Uncontested\/dp\/1625274491\">Blue Ocean Strategy<\/a>: If you&rsquo;re interested in how to explore different strategies for a successful business, I recommend reading this.<\/li><li><a href=\"https:\/\/www.amazon.com\/Good-Great-Some-Companies-Others\/dp\/0066620996\/\">Good to Great<\/a>: This is a fascinating dissection of the characteristics and qualities that define successful companies over the long-term.<\/li><li><a href=\"https:\/\/www.amazon.com\/Essentials-Business-Communication-Ellen-Guffey\/dp\/1337386499\/\">Essentials of Business Communication<\/a>: I&rsquo;m only familiar with the 7th edition of this textbook, but it&rsquo;s a reference I continue to use today when I need to structure a certain kind of written business report or communication.<\/li><\/ul>"},{"title":"Engineering MBA @ RailsConf","link":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-railsconf\/","pubDate":"Mon, 08 Mar 2021 08:12:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/engineering-mba-railsconf\/","description":"<p>I&rsquo;m honored to be speaking as part of RailsConf 2021, which you can join from<a href=\"http:\/\/railsconf.com\/\">wherever<\/a> you are. I hope you&rsquo;ll join <a href=\"https:\/\/railsconf.com\/program\/sessions#session-1136\">my session<\/a>and many others from April 13 - 15, 2021.<\/p><h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>Improve your work as a developer with an introduction to strategic planning,situational leadership, and process management. No balance sheets or incomestatements here; join me to learn the MBA skills valuable to developers withoutthe opportunity costs of lost wages or additional student loans.<\/p><p>Demystify the strategic frameworks your management team may use to makedecisions and learn how you can use those same concepts in your daily work.Explore the synergy one developer achieved by going to business school (sorry,the synergy comment slipped out - old habit).<\/p>"},{"title":"Featured in Ruby Weekly 541","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-541\/","pubDate":"Thu, 25 Feb 2021 18:20:36 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-541\/","description":"<p>I&rsquo;m excited to be in this week&rsquo;s Ruby Weekly, <a href=\"https:\/\/rubyweekly.com\/issues\/541\">#541<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_541.png\"><\/figure><p>The article they included is a <a href=\"https:\/\/kevinjmurphy.com\/posts\/querying-papertrail-object-changes-json\/\">post<\/a>from my employer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/querying-papertrail-object-changes-json\">blog<\/a>about querying for JSON data in your database. The specific use case is usingPaperTrail&rsquo;s versions to query for what changed.<\/p>"},{"title":"Querying PaperTrail Object Changes in JSON","link":"https:\/\/kevinjmurphy.com\/posts\/querying-papertrail-object-changes-json\/","pubDate":"Mon, 15 Feb 2021 16:47:03 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/querying-papertrail-object-changes-json\/","description":"<p><a href=\"https:\/\/github.com\/paper-trail-gem\/paper_trail\">PaperTrail<\/a> may be a fit foryour rails app if you&rsquo;ve ever wanted help answering the question, &ldquo;whatseries of events conspired to put my database in this state?&rdquo; It acts as a timecapsule, storing each change made to instances of your models that you havePaperTrail turned on for.<\/p><p>Let&rsquo;s create a user. Unfortunately, we can&rsquo;t make up our mind about whichemail to use, so we change that a few times.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>create(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;kevin@example.com&#34;<\/span>, <span style=\"color:#e6db74\">first_name<\/span>: <span style=\"color:#e6db74\">&#34;Kevin&#34;<\/span>, <span style=\"color:#e6db74\">last_name<\/span>: <span style=\"color:#e6db74\">&#34;Murphy&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>update(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;kevinfinal@example.com&#34;<\/span>)<\/span><\/span><\/code><\/pre><\/div><p>We have PaperTrail set to <a href=\"https:\/\/github.com\/paper-trail-gem\/paper_trail#only\">track changes<\/a>made to a user&rsquo;s email. Later on, we may need to do some archaeology to figureout why this user&rsquo;s email address isn&rsquo;t &ldquo;km@example.com&rdquo;. Let&rsquo;s investigate howPaperTrail can help with that.<\/p><h2 id=\"using-papertrails-api\">Using PaperTrail&rsquo;s API <a href=\"#using-papertrails-api\">\ud83d\udd17<\/a><\/h2><p>PaperTrail provides a few options to <a href=\"https:\/\/github.com\/paper-trail-gem\/paper_trail#3b-navigating-versions\">navigate<\/a>changes made over the history of our object. We&rsquo;re going to use<code>where_object_changes<\/code> to figure out what happened with &ldquo;km@example.com&rdquo;.<code>where_object_changes<\/code> will find any time the provided attributes changed to<strong>or<\/strong> from the values provided.<\/p><p>Let&rsquo;s first find out if this user&rsquo;s email ever was &ldquo;km@example.com&rdquo;.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>versions<span style=\"color:#f92672\">.<\/span>where_object_changes(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>count<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#ae81ff\">2<\/span><\/span><\/span><\/code><\/pre><\/div><p>Now we know that at some point in time, this user&rsquo;s email was stored as&ldquo;km@example.com&rdquo;. We can hone in on the changes, and specifically only look atthe email address changes.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>versions<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>where_object_changes(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:object_changes<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">.<\/span>map { <span style=\"color:#f92672\">|<\/span>c<span style=\"color:#f92672\">|<\/span> c<span style=\"color:#f92672\">.<\/span>slice(<span style=\"color:#e6db74\">&#34;email&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span>{<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;kevin@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>},<\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;kevinfinal@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>}<span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Any PaperTrail Version has an <code>object_changes<\/code> attribute. The value of thatattribute is a hash. The keys of that hash are the attributes that changed, andthe value is a tuple (as an array) that shows first the value that attributechanged from, and the value it changed to.<\/p><p>Here we&rsquo;re finding all versions that have to do with the email address&ldquo;km@example.com&rdquo; and only showing the changes to the email address.<\/p><p>This first version shows that the email address changed from &ldquo;kevin@example.com&rdquo;to &ldquo;km@example.com&rdquo;. Later on, the second version shows the email addresschanging from &ldquo;km@example.com&rdquo; to &ldquo;kevinfinal@example.com&rdquo;.<\/p><h2 id=\"enhanced-json-querying\">Enhanced JSON Querying <a href=\"#enhanced-json-querying\">\ud83d\udd17<\/a><\/h2><p>If <a href=\"https:\/\/github.com\/paper-trail-gem\/paper_trail#postgresql-json-column-type-support\">PaperTrail versions<\/a>are stored as JSON or JSONB in Postgres, we have some more detailed queries wecan write by digging into Postgres&rsquo; <a href=\"https:\/\/www.postgresql.org\/docs\/9.4\/functions-json.html\">JSON functions<\/a>.<\/p><h3 id=\"changed\">Changed <a href=\"#changed\">\ud83d\udd17<\/a><\/h3><p>Sometimes we may not have an attribute value that we want to search for. We mayneed to first know when an attribute changed to anything at all. Let&rsquo;s look forany time that this user&rsquo;s email attribute changed.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>versions<span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">&#34;object_changes -&gt; &#39;email&#39; IS NOT NULL&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:object_changes<\/span>)<span style=\"color:#f92672\">.<\/span>map { <span style=\"color:#f92672\">|<\/span>c<span style=\"color:#f92672\">|<\/span> c<span style=\"color:#f92672\">.<\/span>slice(<span style=\"color:#e6db74\">&#34;email&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span>{<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;&#34;<\/span>, <span style=\"color:#e6db74\">&#34;kevin@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>},<\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;kevin@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>},<\/span><\/span><span style=\"display:flex;\"><span> {<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;kevinfinal@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>}<span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>The first version is logged when you initially create the row in your database,which is why it changes from the empty string to &ldquo;kevin@example.com&rdquo;. Fromthere, we see the additional updates to email, as all versions have changed theemail address.<\/p><h3 id=\"changing-from\">Changing From <a href=\"#changing-from\">\ud83d\udd17<\/a><\/h3><p>While the prior query gives us <em>less<\/em> specificity than PaperTrail provides with<code>where_object_changes<\/code>, we may also benefit from <em>more<\/em> specificity. Forexample, we may want to know when the user&rsquo;s email changed from&ldquo;km@example.com&rdquo;, and not care about when it changed to &ldquo;km@example.com&rdquo;.<\/p><p>We <strong>can<\/strong> use <code>where_object_changes<\/code>, but to find out which changes are onlychanging the value from &ldquo;km@example.com&rdquo;, we&rsquo;ll need to scan the resultsourselves or in memory, rather than relying on the database.<\/p><p>However, we can construct a query ourselves to tell us only when an attributechanged from a value.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>versions<span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">&#34;object_changes -&gt;&gt;&#39;email&#39; ILIKE &#39;[<\/span><span style=\"color:#ae81ff\">\\&#34;<\/span><span style=\"color:#e6db74\">km@example.com<\/span><span style=\"color:#ae81ff\">\\&#34;<\/span><span style=\"color:#e6db74\">,%&#39;&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:object_changes<\/span>)<span style=\"color:#f92672\">.<\/span>map { <span style=\"color:#f92672\">|<\/span>c<span style=\"color:#f92672\">|<\/span> c<span style=\"color:#f92672\">.<\/span>slice(<span style=\"color:#e6db74\">&#34;email&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span>{<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;kevinfinal@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>}<span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Remember that the object changes of an attribute are stored as a tuple in anarray. The first value in the array is the value the attribute changed from, andthe second value is the value the attribute changed to.<\/p><p>This query looks within the <code>object_changes<\/code> attribute, and checks to see if thevalue of the <code>email<\/code> field within that attribute has the email address we&rsquo;relooking for as the first element in the tuple. The opening bracket before theemail address in escaped quotes is the start of the array\/tuple.<\/p><h3 id=\"changing-to\">Changing To <a href=\"#changing-to\">\ud83d\udd17<\/a><\/h3><p>We can also construct a query to look for versions on the other side of theequation. We can find all instances where the attribute changed <em>to<\/em> aparticular value.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> user<span style=\"color:#f92672\">.<\/span>versions<span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">&#34;object_changes -&gt;&gt;&#39;email&#39; ILIKE &#39;[%,<\/span><span style=\"color:#ae81ff\">\\&#34;<\/span><span style=\"color:#e6db74\">km@example.com<\/span><span style=\"color:#ae81ff\">\\&#34;<\/span><span style=\"color:#e6db74\">]&#39;&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>pluck(<span style=\"color:#e6db74\">:object_changes<\/span>)<span style=\"color:#f92672\">.<\/span>map { <span style=\"color:#f92672\">|<\/span>c<span style=\"color:#f92672\">|<\/span> c<span style=\"color:#f92672\">.<\/span>slice(<span style=\"color:#e6db74\">&#34;email&#34;<\/span>) }<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span>{<span style=\"color:#e6db74\">&#34;email&#34;<\/span><span style=\"color:#f92672\">=&gt;[<\/span><span style=\"color:#e6db74\">&#34;kevin@example.com&#34;<\/span>, <span style=\"color:#e6db74\">&#34;km@example.com&#34;<\/span><span style=\"color:#f92672\">]<\/span>}<span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><p>When looking for what the value changed to, we want to look in our tuple to findthe email address in question as the last element, which will be preceded by acomma and have an ending bracket after it.<\/p><h2 id=\"trailing-off\">Trailing Off <a href=\"#trailing-off\">\ud83d\udd17<\/a><\/h2><p>Thanks to storing our PaperTrail versions in a JSON format, we were able tosneak in an applied lesson on Postgres&rsquo; powerful JSON functions andoperators while also reviewing the structure of data that&rsquo;s used by PaperTrailto track any versions of our database records.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/querying-papertrail-object-changes-json\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Featured in Ruby Weekly","link":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-539\/","pubDate":"Thu, 11 Feb 2021 15:20:36 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/ruby-weekly-539\/","description":"<p>I&rsquo;m honored to be in this week&rsquo;s Ruby Weekly, <a href=\"https:\/\/rubyweekly.com\/issues\/539\">#539<\/a>.<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_539.png\"><\/figure><p>The article they included is a <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">post<\/a> from my employer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/using-sonic-pi-to-play-music-with-ruby\">blog<\/a> that waspart of my self-described &ldquo;Software Design Concert Series&rdquo;. You can read it tolearn about how to trigger Sonic Pi from within your ruby code.<\/p>"},{"title":"Nokogiri Notoriety","link":"https:\/\/kevinjmurphy.com\/posts\/nokogiri-notoriety\/","pubDate":"Wed, 13 Jan 2021 18:20:36 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/nokogiri-notoriety\/","description":"<p>Last week, I wrote a <a href=\"https:\/\/kevinjmurphy.com\/posts\/caching-all-native-gem-platforms\/\">post<\/a> onmy employer&rsquo;s <a href=\"https:\/\/blog.thegnar.co\/caching-all-native-gem-platforms\">blog<\/a>about how to set up the correct bundler configurationto support caching multiple platforms of gems. This came up with the latest Nokogirirelease, where pre-built binaries for various platforms are now available, soyou don&rsquo;t need to install from source.<\/p><p>That post has been featured in Ruby Weekly <a href=\"https:\/\/rubyweekly.com\/issues\/534\">#534<\/a>!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/ruby_weekly_534.png\"><\/figure><p>I was also humbled to receive a tweet reply from Nokogiri maintainer Mike Dalessio, which says:<\/p><blockquote><p>I&rsquo;ve linked out to your post from the &ldquo;Troubleshooting&rdquo; section of the installation guide. Thanks again!<\/p><\/blockquote><p>And <a href=\"https:\/\/nokogiri.org\/tutorials\/installing_nokogiri.html#using-vendorcache-to-deploy-to-another-architecture\">here I am<\/a>in the Nokogiri tutorials!<\/p><figure class=\"mid\"><img src=\"https:\/\/kevinjmurphy.com\/img\/nokogiri_tutorial.png\"><\/figure><p>The commit adding me to the guide is <a href=\"https:\/\/github.com\/sparklemotion\/nokogiri.org\/commit\/173ecdc110c738d0c5708934eb51d03e8e9f418d\">here<\/a>.<\/p><p>Thanks to Peter Cooper for featuring me in Ruby Weekly, and Mike Dalessio foradding me to Nokogiri&rsquo;s guides!<\/p>"},{"title":"Caching All Native Ruby Gem Platforms","link":"https:\/\/kevinjmurphy.com\/posts\/caching-all-native-gem-platforms\/","pubDate":"Wed, 06 Jan 2021 12:20:07 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/caching-all-native-gem-platforms\/","description":"<h2 id=\"tldr\">TL;DR <a href=\"#tldr\">\ud83d\udd17<\/a><\/h2><p>Some dependencies, like nokogiri, ship with multiple libraries for differentarchitectures. If you cache your gems, you may need to cache multiple platforms,because your development team is spread across various platforms or you deployto a different platform. To do this, you can use:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle cache <span style=\"color:#75715e\"># cache gems in vendor\/cache <\/span><\/span><\/span><span style=\"display:flex;\"><span>bundle lock --add-platform x86_64-linux <span style=\"color:#75715e\"># add additional platforms <\/span><\/span><\/span><span style=\"display:flex;\"><span>bundle package --all-platforms <span style=\"color:#75715e\"># cache multiple platforms <\/span><\/span><\/span><\/code><\/pre><\/div><p>On bundler version 1.x, add:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle config specific_platform true <\/span><\/span><\/code><\/pre><\/div><h2 id=\"native-nokogiri\">Native Nokogiri <a href=\"#native-nokogiri\">\ud83d\udd17<\/a><\/h2><p>Nokogiri 1.11.0 has been released, and one of the exciting <a href=\"https:\/\/github.com\/sparklemotion\/nokogiri\/blob\/007662fc216902a5ae186cb78b0d46f7f48b8d92\/CHANGELOG.md#v1110--2021-01-03\">updates<\/a>is the inclusion of pre-compiled native gems for various platforms! If you&rsquo;reusing a supported platform, your days of installing nokogiri with nativeextensions may be over. These <a href=\"https:\/\/nokogiri.org\/tutorials\/installing_nokogiri.html#installing-native-gems\">changes<\/a>result, &ldquo;in much faster installation and more reliable installation&rdquo;. Manythanks to the maintainers and contributors for this great update.<\/p><p>Updating to these pre-compiled gems should be a seamless experience. Bundlerwill grab the appropriate pre-compiled <code>.gem<\/code> file, if you&rsquo;re on asupported version, and use that. However, if you <a href=\"https:\/\/bundler.io\/man\/bundle-cache.1.html\">cache<\/a>your gems, and you&rsquo;d like to cache multiple platforms, you have a few moresteps to complete.<\/p><h2 id=\"cache-hit\">Cache Hit <a href=\"#cache-hit\">\ud83d\udd17<\/a><\/h2><p>Gem dependencies can be cached along with your app and then you can use thatcache to retrieve your application&rsquo;s dependencies, rather than RubyGems.We take advantage of this on a number of projects for various reasons, but themost important one that requires all gems to be vendored is that someapplications are deployed to, and the deployments are created in, environmentswhere they <strong>cannot<\/strong> access <a href=\"https:\/\/rubygems.org\/\">RubyGems<\/a> directly.<\/p><p>We need to tell bundler to cache our gems.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle cache <\/span><\/span><\/code><\/pre><\/div><p>Running that on an existing application will add <code>.gem<\/code> files into the <code>vendor\/cache<\/code><a href=\"https:\/\/github.com\/kevin-j-m\/bundler_2_cache_all_platforms\/commit\/75c7ddb8f569e33cdd09e0ee1c4a377885318416\">directory<\/a>.<\/p><h2 id=\"platform-dependence\">Platform Dependence <a href=\"#platform-dependence\">\ud83d\udd17<\/a><\/h2><p>You need to tell bundler that you require multiple platforms. In the case ofthis example, I&rsquo;m developing on a computer running macOS, so installing nokogiriwill <a href=\"https:\/\/github.com\/kevin-j-m\/bundler_2_cache_all_platforms\/commit\/61b73d01e929ec3b43c43ae9ea1af00df273a4b1\">give me<\/a>the pre-compiled gem for that architecture. That&rsquo;s great, but I also need thelinux native gem for my deployment environments.<\/p><p>First, I need to tell <a href=\"https:\/\/bundler.io\/v2.0\/bundle_lock.html\">bundler<\/a> toadd the platform.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle lock --add-platform x86_64-linux<\/span><\/span><\/code><\/pre><\/div><p>After doing that, the <code>Gemfile.lock<\/code> file is <a href=\"https:\/\/github.com\/kevin-j-m\/bundler_2_cache_all_platforms\/commit\/d36495a715c26fb1f674021ffc19dc61c1787e4f\">updated<\/a>to list that platform.<\/p><h2 id=\"platform-independence\">Platform Independence <a href=\"#platform-independence\">\ud83d\udd17<\/a><\/h2><p>However, even if you add the platform <em>before<\/em> installing the dependency, addingthe platform will still not retrieve and cache both platform&rsquo;s <code>.gem<\/code> files.We also need to tell bundler to cache those<a href=\"https:\/\/bundler.io\/man\/bundle-cache.1.html#SUPPORT-FOR-MULTIPLE-PLATFORMS\">other platforms<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle package --all-platforms<\/span><\/span><\/code><\/pre><\/div><p>Now our other platform is <a href=\"https:\/\/github.com\/kevin-j-m\/bundler_2_cache_all_platforms\/commit\/a9ef611df04114921e71b4d8e4100894d00e2925\">cached<\/a>,along with the existing platforms.<\/p><p>If you are using Bundler version 1.x, you may also need to set the<code>specific_platform<\/code> configuration <a href=\"https:\/\/github.com\/rubygems\/bundler\/issues\/5863#issuecomment-315800951\">setting<\/a>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>bundle config specific_platform true<\/span><\/span><\/code><\/pre><\/div><p>Now you should have all your gem dependencies cached across all platformsspecified in your <code>Gemfile.lock<\/code>. You no longer need to compile nokogiri!<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/caching-all-native-gem-platforms\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Stringing Code Together to Play Music","link":"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/","pubDate":"Tue, 15 Dec 2020 07:03:46 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi To Play Music With Ruby<\/a><\/li><li><strong>Stringing Code Together To Play Music<\/strong><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>In our <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">last post<\/a>, I talked about how I built an interface to <a href=\"https:\/\/sonic-pi.net\">Sonic Pi<\/a> whenI was preparing my <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a> talk about Ruby&rsquo;s <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> module. At theend of that post, we could send sounds to Sonic Pi. Today, we&rsquo;ll have our codeplay the guitar, and send those sounds to our amplifier.<\/p><h2 id=\"string-theory\">String Theory <a href=\"#string-theory\">\ud83d\udd17<\/a><\/h2><p>A guitar is a string instrument, and each of those strings make a sound when youplay them. For this example we&rsquo;ll focus on the happy path, which is thatplucking the string plays the expected note. The code I built also considersthat strings can break, and attempting to play broken strings won&rsquo;t work. Youcan look at the <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/instruments\/guitar\/string.rb#L20-L28\">full implementation<\/a>to see how that works.<\/p><p>Plucking an individual string creates a new sound.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">String<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pluck<\/span>(<span style=\"color:#e6db74\">fret<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> play_note(fret)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play_note<\/span>(fret)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">StringSound<\/span><span style=\"color:#f92672\">.<\/span>new(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">string_number<\/span>: @number,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">tuning_note<\/span>: tuning_note,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">fret_number<\/span>: fret,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The <code>@number<\/code> variable is which string on the guitar it is, with index 0 beingthe low E, and index 5 being the high E, in standard tuning. The <code>tuning_note<\/code>is what note that string is tuned to, because any string <strong>can<\/strong> be tuned to anynote. Again, for simplicity here, we&rsquo;ll assume standard tuning (EADGBE).<\/p><p>Our <code>StringSound<\/code> class converts that information into the command we&rsquo;ll send toSonic Pi. All notes in Sonic Pi are represented with a <a href=\"https:\/\/sonic-pi.net\/tutorial#section-2-1\">number<\/a>,and we can also use &ldquo;traditional&rdquo; note names, passed to it as a symbol. We canuse that to figure out the note our string would play if you plucked it withoutpressing down on a fret.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">StringSound<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">playable_note_root<\/span><\/span><\/span><span style=\"display:flex;\"><span> playable_note_key<span style=\"color:#f92672\">.<\/span>dig(@string_number, @tuning_note)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">playable_note_key<\/span><\/span><\/span><span style=\"display:flex;\"><span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">0<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">e<\/span>: <span style=\"color:#e6db74\">:e2<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">1<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">a<\/span>: <span style=\"color:#e6db74\">:a2<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">2<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">d<\/span>: <span style=\"color:#e6db74\">:d3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">3<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">g<\/span>: <span style=\"color:#e6db74\">:g3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">4<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">b<\/span>: <span style=\"color:#e6db74\">:b3<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#ae81ff\">5<\/span> <span style=\"color:#f92672\">=&gt;<\/span> { <span style=\"color:#e6db74\">e<\/span>: <span style=\"color:#e6db74\">:e4<\/span> },<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The number next to the note (the <code>2<\/code> in <code>:e2<\/code> for the low E string) representsthe octave.<\/p><p>A helpful thing here is that the note is still a number to Sonic Pi. We can addthe fret number pressed on the string to the root note of the string and SonicPi will know what note that is.We&rsquo;ll construct a Sonic Pi command to send to our <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">amplifier<\/a>to play that note.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">StringSound<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">amp_value<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;(note(:<\/span><span style=\"color:#e6db74\">#{<\/span>playable_note_root<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">) + <\/span><span style=\"color:#e6db74\">#{<\/span>@fret_number<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">)&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This is all in a string (the data type, not the part of the instrument),because we&rsquo;re going to pass it to Sonic Pi via the<a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\/\">sonic-pi-cli gem<\/a>.This is going to execute the <code>note<\/code> method in Sonic Pi to play that singletone.<\/p><h2 id=\"plucking-a-single-string\">Plucking a Single String <a href=\"#plucking-a-single-string\">\ud83d\udd17<\/a><\/h2><p>Our guitarist is interfacing with the guitar as a whole, which is <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">composed<\/a> ofmany strings. They&rsquo;ll first place their fingers on the neck of the guitar.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">FingerPlacement<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:fret<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:string_number<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>And pluck an individual string with that placement.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">pick<\/span>(finger_placement, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> result <span style=\"color:#f92672\">=<\/span> strings<span style=\"color:#f92672\">[<\/span>finger_placement<span style=\"color:#f92672\">.<\/span>string_number<span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: finger_placement<span style=\"color:#f92672\">.<\/span>fret)<\/span><\/span><span style=\"display:flex;\"><span> @amplifier<span style=\"color:#f92672\">.<\/span>play(sound_output(<span style=\"color:#e6db74\">&#34;play <\/span><span style=\"color:#e6db74\">#{<\/span>result<span style=\"color:#f92672\">.<\/span>amp_value<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span>, <span style=\"color:#e6db74\">duration<\/span>: duration))<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Here our guitar is adding details to the command that we&rsquo;ll send toSonic Pi. We have the information about the note to play from the string, butnow we want it to sound like a note from a guitar, and we&rsquo;ll rely on theguitarist to say how long to play the note for (the duration).<\/p><p>We can do this in Sonic Pi by specifying the synthesizer to use when playing thenote, and we&rsquo;ll choose one that sounds like a guitar.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">sound_output<\/span>(play_operation, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;with_synth :pluck do&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#e6db74\">#{<\/span>play_operation<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">, release: <\/span><span style=\"color:#e6db74\">#{<\/span>duration<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;end&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">].<\/span>join(<span style=\"color:#e6db74\">&#34;<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>strip<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>If you wanted to play this directly in Sonic Pi&rsquo;s IDE, it would look morefamiliar:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>with_synth <span style=\"color:#e6db74\">:pluck<\/span> <span style=\"color:#66d9ef\">do<\/span><\/span><\/span><span style=\"display:flex;\"><span> play note(<span style=\"color:#e6db74\">:e2<\/span> <span style=\"color:#f92672\">+<\/span> <span style=\"color:#ae81ff\">1<\/span>), <span style=\"color:#e6db74\">release<\/span>: <span style=\"color:#ae81ff\">1<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>However, we need to package this all up in a string to then send that commandover to Sonic Pi via the sonic-pi-cli gem.<\/p><p>Our amplifier, passed in via <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency injection<\/a>,then takes that command and sends it to Sonic Pi, producing a sound!<\/p><h2 id=\"strike-a-chord\">Strike a Chord <a href=\"#strike-a-chord\">\ud83d\udd17<\/a><\/h2><p>Sonic Pi already knows how to play <a href=\"https:\/\/sonic-pi.net\/tutorial#section-8-2\">chords<\/a>,so this could be a quick section; however, we&rsquo;re going to replicate thatfunctionality a little differently. We&rsquo;re doing this because of the reality Imentioned when talking about strings - and that is, they can break. If a stringis broken, the note in the chord that string would regularly play shouldn&rsquo;t beheard.<\/p><p>As such, we need to go string by string to determine the notes to play. Eventhough the reasoning is to handle broken strings, we&rsquo;re not going to considerthat case in this explanation. You can view the <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/instruments\/guitar.rb#L23-L41\">fullimplementation<\/a>to see how that&rsquo;s handled.<\/p><p>We first need to know which notes we should play:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">strum<\/span>(chord, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> notes <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[<\/span><\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>first_fret),<\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">1<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>second_fret),<\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>third_fret),<\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">3<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>fourth_fret),<\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">4<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>fifth_fret),<\/span><\/span><span style=\"display:flex;\"><span> strings<span style=\"color:#f92672\">[<\/span><span style=\"color:#ae81ff\">5<\/span><span style=\"color:#f92672\">].<\/span>pluck(<span style=\"color:#e6db74\">fret<\/span>: chord<span style=\"color:#f92672\">.<\/span>sixth_fret),<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">].<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:amp_value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ll then take all of those notes and pass them to our amplifier, using SonicPi&rsquo;s <code>play_pattern_timed<\/code> <a href=\"https:\/\/github.com\/hashbangstudio\/Sonic-Pi-Examples\/blob\/master\/10-play-pattern-timed.rb\">method<\/a>.This also allows us to define a time between each note, so we can place a smallamount of time in between each to simulate the time it would take your hand tocomplete a downstroke across all the strings.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">strum<\/span>(chord, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> notes <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[...].<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:amp_value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> @amplifier<span style=\"color:#f92672\">.<\/span>play(<\/span><\/span><span style=\"display:flex;\"><span> sound_output(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">&#34;play_pattern_timed [<\/span><span style=\"color:#e6db74\">#{<\/span>pattern_notes<span style=\"color:#f92672\">.<\/span>join(<span style=\"color:#e6db74\">&#34;, &#34;<\/span>)<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">], 0.05&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">duration<\/span>: duration,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The 0.05 is our amount of time it&rsquo;ll take to pluck from one string to the nextwhen playing a chord.<\/p><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Combining a few key software design principles, we were able to create aflexible, extensible, and testable system for playing music over the course of afew blog posts.<\/p><p>We&rsquo;re now armed with an amplifier that knows how to communicate with <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Sonic Pi<\/a>that&rsquo;s passed in to our guitar via <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency injection<\/a> (but could send the notes anywhere as long as the injected class <a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">responds<\/a> to the right methods). Our guitar is <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">composed<\/a> of various strings, each of which are responsible for knowing what sound to make.<\/p><p>Given a songwriter who knows how to<a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">consistently write<\/a> for our band, we canplay chords and individual notes on our guitar as the <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/songs\/the_line_begins_to_blur.rb\">song<\/a> requires.<\/p><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/GncJGXdS6R8?rel=0\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><p>If you listen closely at :14, you can hear a string break. Even with theseprinciples in place, mistakes and errors happen. Make sure your system isprepared to handle errors in a fault-tolerant way - but that&rsquo;s a different blogseries altogether. Thanks for joining me in this exploration.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/stringing-code-together-to-play-music\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Using Sonic Pi to Play Music With Ruby","link":"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/","pubDate":"Mon, 14 Dec 2020 18:48:24 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li><strong>Using Sonic Pi To Play Music With Ruby<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together To Play Music<\/a><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>My <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a>talk about Ruby&rsquo;s <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> moduleuses examples about playing live music. As such, I had the ambitious goal ofdelivering a live performance of some music during the presentation. This endedup getting cut for a variety of reasons (time, concern about the audio workingon the streaming platform, the reality of ambition turning into actual work todo), but I built out the structure to support this for one instrument, theguitar. I integrated this into my <a href=\"https:\/\/kevinjmurphy.com\/posts\/play-guitar\/\">RubyConf Mini 2022<\/a>talk, though with some <a href=\"https:\/\/kevinjmurphy.com\/posts\/revisiting-calling-sonic-pi-from-ruby\/\">modifications<\/a>.<\/p><p>This is the first of two posts that&rsquo;ll describe the work that I did tosupport this.<\/p><p>First, I had to figure out if it was possible to make this happen. I wanted tohook into my existing code samples and trigger musical notes from them somehow.As such, I decided to build my first amplifier, virtually, without fear ofblowing up any capacitors.<\/p><h2 id=\"parts-list\">Parts List <a href=\"#parts-list\">\ud83d\udd17<\/a><\/h2><p>In the earlier post on <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency injection<\/a>,I created a <code>PracticeAmplifier<\/code> <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/practice_amplifier.rb\">class<\/a>that did nothing so I could use it in tests, rather than the &ldquo;regular&rdquo; amplifier.<\/p><p>What the &ldquo;regular&rdquo; <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/amplifier.rb\">amplifier<\/a>does is interface with <a href=\"https:\/\/sonic-pi.net\/\">Sonic Pi<\/a>, which is awesomesoftware that&rsquo;ll make sound and music driven by code. Sonic Pi comes withan <a href=\"https:\/\/sonic-pi.net\/tutorial.html#section-1-2\">IDE<\/a> of sorts that you canuse to program the composition you&rsquo;d like to play, and get immediate feedbackfrom hearing how your code is translated into audio. It&rsquo;s a great way to losetrack of time for a night or two (or more). However, I was envisioningcontrolling my audio from the code examples directly. I didn&rsquo;t want to have towork within the IDE.<\/p><p>To get around using the IDE directly, I found the <a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\">sonic-pi-cli<\/a>gem. This gem works with Sonic Pi versions 2.7 - 3.2. Its principal use case is to be used directly in the<a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\/blob\/c4280f98edcec4de99801d013ec946cc47787932\/bin\/sonic_pi\">terminal<\/a>.However, it&rsquo;s a gem, and written in ruby, and the core functionality isavailable in a <a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\/blob\/c4280f98edcec4de99801d013ec946cc47787932\/lib\/sonic_pi.rb\">class<\/a>that you can use in any of your code.<\/p><h2 id=\"wiring-schematic\">Wiring Schematic <a href=\"#wiring-schematic\">\ud83d\udd17<\/a><\/h2><p>With enough knowledge and conviction to be dangerous, I set out wiring up myamplifier. The CLI requires that Sonic Pi itself is running, and first ensuresit can communicate with it - and to do so, it needs to know what port thesoftware is running on. Sonic Pi used to always run on the same port; however,it has since changed to run on a <a href=\"https:\/\/github.com\/sonic-pi-net\/sonic-pi\/commit\/d245d93c5b797ad76fa333f829c32d67480af96c\">dynamically-determined<\/a> port.<\/p><p>The CLI already implemented the functionality to <a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\/blob\/20a18f91b4aa24de9f4b187aa20c69334ddf0329\/bin\/sonic_pi#L13-L33\">find the port<\/a>to send to the <code>SonicPi<\/code> class, so for demonstration purposes, I copied that inmy constructor.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @port <span style=\"color:#f92672\">=<\/span> find_port<\/span><\/span><span style=\"display:flex;\"><span> @speaker <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">SonicPi<\/span><span style=\"color:#f92672\">.<\/span>new(@port)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">private<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">find_port<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\"># Code from sonic-pi-cli<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Needing to find the port is now something that the <code>SonicPi<\/code> class can do <a href=\"https:\/\/github.com\/Widdershin\/sonic-pi-cli\/pull\/23\">by itself<\/a>as of version v0.2.0; however, this work preceded that.<\/p><p>The rest of the functionality in the <code>Amplifier<\/code> class is now to <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">delegate<\/a> commands to the <code>@speaker<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Amplifier<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span>(sound)<\/span><\/span><span style=\"display:flex;\"><span> @speaker<span style=\"color:#f92672\">.<\/span>run(sound)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Using this amplifier still requires knowing all the correct<a href=\"https:\/\/sonic-pi.net\/tutorial.html#section-2-1\">commands<\/a> to send to Sonic Pi,and Sonic Pi must be running; however, we can now trigger it to execute thesecommands from outside of its IDE. We have a way to <em>send<\/em> soundout of our ruby code.<\/p><p>In our next post, we&rsquo;ll take a look at how we <em>generate<\/em> the sound to send froma <a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">guitar<\/a> to an amplifier.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/using-sonic-pi-to-play-music-with-ruby\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Inheritance: Derivative Songwriting","link":"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/","pubDate":"Thu, 10 Dec 2020 16:56:38 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li><strong>Inheritance: Derivative Songwriting<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi To Play Music With Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together To Play Music<\/a><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>Inheritance sets up a relationship or a taxonomy between classes to allow forcode reuse. It is both a commonly reached for and commonly derided tool whichhas its place, but must be wielded with care. We&rsquo;ll use inheritance to write newsongs for our concert setlist, an example which comes from my <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a>talk about Ruby&rsquo;s<a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> module.<\/p><h2 id=\"song-structure\">Song Structure <a href=\"#song-structure\">\ud83d\udd17<\/a><\/h2><p>When you create a song, it needs a name (or at least a working title) and aseries of notes. The notes may change over time, and the title may be refined,but for our purposes, we&rsquo;re not calling it a song until there&rsquo;s a bit more thanan empty page.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Song<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(<span style=\"color:#e6db74\">notes<\/span>: <span style=\"color:#f92672\">[]<\/span>, name:)<\/span><\/span><span style=\"display:flex;\"><span> @notes <span style=\"color:#f92672\">=<\/span> notes<\/span><\/span><span style=\"display:flex;\"><span> @name <span style=\"color:#f92672\">=<\/span> name<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When you&rsquo;re writing songs for a band or yourself, you need to be able to play thesong. In this example, our song is written for a band that has a known number ofinstruments.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Song<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> @notes<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>note<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @guitar<span style=\"color:#f92672\">.<\/span>play(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @vocal<span style=\"color:#f92672\">.<\/span>sing(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @drum<span style=\"color:#f92672\">.<\/span>hit(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @keyboardist<span style=\"color:#f92672\">.<\/span>program(note) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>For every note (representing a beat or measure of the song) each member of theband needs to play their part simultaneously. All of these instruments playingtogether note for note comprise the song.<\/p><h2 id=\"on-repeat\">On Repeat <a href=\"#on-repeat\">\ud83d\udd17<\/a><\/h2><p>A touring band is going to play the same song <strong>many<\/strong> times night after night.For each concert on the tour, the band needs to construct a setlist of all thesongs that they&rsquo;ll play that night, and in what order.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Setlist<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">add_song<\/span>(song)<\/span><\/span><span style=\"display:flex;\"><span> @songs <span style=\"color:#f92672\">&lt;&lt;<\/span> song<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Transcribing all the notes for each song over and over again for every concertwould be tedious and unnecessary. To save all that work, each song that couldappear in the band&rsquo;s setlist is catalogued as a separate class.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TheLineBeginsToBlur<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @name <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#34;The Line Begins To Blur&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span> @notes <span style=\"color:#f92672\">=<\/span> verse_1 <span style=\"color:#f92672\">+<\/span> chorus <span style=\"color:#f92672\">+<\/span> verse_2 <span style=\"color:#f92672\">+<\/span> chorus <span style=\"color:#f92672\">+<\/span> solo <span style=\"color:#f92672\">+<\/span> outro<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We don&rsquo;t need to accept any arguments for the name of the song or the notesbecause it&rsquo;s already a fully-formed song. We&rsquo;re not going to change thearrangement in the middle of the tour. However, we <strong>do<\/strong> need to be able toplay the song. As such, let&rsquo;s copy and paste the <code>play<\/code> method as something wecan do for our specific song here.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TheLineBeginsToBlur<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> @notes<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>note<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @guitar<span style=\"color:#f92672\">.<\/span>play(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @vocal<span style=\"color:#f92672\">.<\/span>sing(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @drum<span style=\"color:#f92672\">.<\/span>hit(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @keyboardist<span style=\"color:#f92672\">.<\/span>program(note) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>This is great because we now have a stable of songs we can pull from every nightwhen creating our setlist; however, rewriting the <code>play<\/code> method in each song isnot great. If the implementation of <code>play<\/code> needs to change, we need topropagate that change across every song. If we forget to add a <code>play<\/code> method toone of our songs, everyone is going to look foolish when the band is staringblankly at each other, unsure of what to do.<\/p><h2 id=\"composing-a-song\">Composing a Song <a href=\"#composing-a-song\">\ud83d\udd17<\/a><\/h2><p>Taking a note from our earlier post on<a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">composition and delegation<\/a>, we canbuild a class that&rsquo;s solely responsible for playing the song.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">SongPerformer<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(notes)<\/span><\/span><span style=\"display:flex;\"><span> @notes <span style=\"color:#f92672\">=<\/span> notes<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> @notes<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>note<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @guitar<span style=\"color:#f92672\">.<\/span>play(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @vocal<span style=\"color:#f92672\">.<\/span>sing(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @drum<span style=\"color:#f92672\">.<\/span>hit(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @keyboardist<span style=\"color:#f92672\">.<\/span>program(note) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>All of our songs can then use that performer and delegate the responsibility ofplaying to it.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TheLineBeginsToBlur<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">SongPerformer<\/span><span style=\"color:#f92672\">.<\/span>new(@notes)<span style=\"color:#f92672\">.<\/span>play<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We have now isolated the responsibility of playing the song to one place. If weneed to change the way in which songs are played in totality, we can do so inthe <code>SongPerformer<\/code> and that change will be reflected in all of our songs. Wecan even <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency inject<\/a> the performer classinto the song, allowing us to set up different arrangements of the same song.Even with those benefits, we <em>do<\/em> still have to remember to implement a <code>play<\/code>method that calls our <code>SongPerformer<\/code>.<\/p><p>There is another option we can explore: inheritance.<\/p><h2 id=\"playing-the-hits\">Playing the Hits <a href=\"#playing-the-hits\">\ud83d\udd17<\/a><\/h2><p>We can leverage our existing, generic, <code>Song<\/code> class and have all of our classesabout specific songs <em>inherit<\/em> the behavior of the <code>Song<\/code> class.<\/p><p>By doing this, our different songs don&rsquo;t need to implement the <code>play<\/code> method.They&rsquo;ll get this behavior from <code>Song<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">TheLineBeginsToBlur<\/span> <span style=\"color:#f92672\">&lt;<\/span> <span style=\"color:#66d9ef\">Song<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">super<\/span>(<\/span><\/span><span style=\"display:flex;\"><span> name: <span style=\"color:#e6db74\">&#34;The Line Begins To Blur&#34;<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">notes<\/span>: verse_1 <span style=\"color:#f92672\">+<\/span> chorus <span style=\"color:#f92672\">+<\/span> verse_2 <span style=\"color:#f92672\">+<\/span> chorus <span style=\"color:#f92672\">+<\/span> solo <span style=\"color:#f92672\">+<\/span> outro,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We denote that we&rsquo;re inheriting from the <code>Song<\/code> class with <code>&lt; Song<\/code>. <code>Song<\/code> isour &ldquo;base class&rdquo;. In our constructor, we then call <code>Song<\/code>&rsquo;s constructor with<code>super<\/code>, passing in the title of the song and the notes that should be playedwith the song. <code>TheLineBeginsToBlur<\/code> has no reference to <code>play<\/code> in its classdefinition. It still responds to it because <code>Song<\/code> does, and we&rsquo;reinheriting all of <code>Song<\/code>s behavior.<\/p><p>When we discussed composition, we mentioned Sandi Metz&rsquo;s <a href=\"https:\/\/www.poodr.com\/\">Practical Object-Oriented Design In Ruby<\/a>for her recommendation to use composition when modeling a <em>has a<\/em> relationship.In that same section, she recommends using inheritance when you encounter an<em>is a<\/em> relationship. In our case, a particular song is a <strong>specialized<\/strong>version of our <code>Song<\/code> class.<\/p><p>Inheritance is a common design choice in Object-Oriented languages. Specificallyin Ruby, if you&rsquo;ve worked with Rails, then you&rsquo;ve likely used inheritance allover the place. All of your models inherit from <code>ApplicationRecord<\/code> (ultimatelyinheriting from <code>ActiveRecord::Base<\/code>) and all of your controllers inheritfrom <code>ApplicationController<\/code> (ultimately inheriting from<code>ActionController::Base<\/code>).<\/p><h2 id=\"a-measured-approach\">A Measured Approach <a href=\"#a-measured-approach\">\ud83d\udd17<\/a><\/h2><p>Inheritance does come with some drawbacks. Enough that it&rsquo;s commonly recommendedto avoid. You may have encountered the phrase, &ldquo;prefer composition overinheritance&rdquo; before. Let&rsquo;s discuss why that is.<\/p><h3 id=\"transparency\">Transparency <a href=\"#transparency\">\ud83d\udd17<\/a><\/h3><p>Inheritance makes it more difficult to know what behaviors a particular classhas. None of our song classes that inherit from <code>Song<\/code> have a <code>play<\/code> methodin their class definition. However, because they all inherit from <code>Song<\/code>, theyall respond to <code>play<\/code>. Determining that is not obvious based on a quick readingof the class.<\/p><h3 id=\"limitations-of-base-class\">Limitations of Base Class <a href=\"#limitations-of-base-class\">\ud83d\udd17<\/a><\/h3><p>Any inheriting classes shouldn&rsquo;t necessarily do things differently than how thebase class does. Of course, you <em>can<\/em> do this, but it should be used veryjudiciously. We could redefine the <code>play<\/code> method in a particular class - sharingthe rest of the behavior and redefining <code>play<\/code> for our one-off specialexception. The issue is that these exceptions start to pile up, we end upchipping away at the commonality, and the shared understanding of what it meansto inherit from the base class gets eroded with each change that seems smallon its own.<\/p><p>For our songs, if we suddenly need to write a song for a string quartet, our<code>Song<\/code> class isn&rsquo;t helpful. It assumes a guitar, vocalist, drummer, andkeyboardist. While particularly in Ruby we have an out by being able to redefineany method definition, from a design perspective, we should be willing to acceptthe limitations that inheritance places on us within the scope of our domain.If those limitations cannot be respected, then consider another organizationalstructure, like composition.<\/p><h3 id=\"future-inflexibility\">Future Inflexibility <a href=\"#future-inflexibility\">\ud83d\udd17<\/a><\/h3><p>It&rsquo;s often impossible to know how your system will evolve over time. Inheritancecan lock you in to a very specific representation of how your system should bemodeled, and the assumptions that went into developing that structure may nothold true as features are needed to be added and the needs that the applicationmust serve grow.<\/p><p>This rigidity over time ends up getting pushed and strainedenough that maintaining inheritance structures becomes difficult. In myopinion, it is this long-view perspective that becomes the principal reason whyinheritance is sparsely recommended by practitioners. It can work great aslong as you have perfect knowledge about both the current and future state ofyour system. The reality is, it&rsquo;s extremely rare to be in that situation.<\/p><p>In this example, our application is modeling a concert tour for one band, themembers and makeup of which <strong>shouldn&rsquo;t<\/strong> change throughout the course of thetour. We&rsquo;ve made the bet that even if the guitarist we start the tour with isreplaced, there will still <strong>be<\/strong> a guitarist, and we will not have picked up afrench horn player along the way to play two of the songs. From a practicalitystandpoint, it&rsquo;s reasonable to be tied to this rigid structure of how to playeach of these songs on stage in the context of this application. However, fromthe onset, we&rsquo;ve already identified one way in which this structure may comeback to haunt us.<\/p><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Inheritance is often reached for as a quick and easy way to achieve code reuse.It does just that; however, it imposes limitations and constraints on your systemthat can make it more difficult or painful to change over time. Thoselimitations may be intentional and required guardrails - but often times, theyend up being factors that cause pain, tears, multiple &ldquo;code spikes&rdquo;, and&ldquo;technical debt sprints&rdquo; to allow for needed future functionality. Inheritanceshouldn&rsquo;t be avoided wholesale based on this, but it should be carefully andjudiciously applied in your systems.<\/p><p>Our next post will move a little further from theory and explore how to build an<a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">interface to Sonic Pi<\/a>,so that these principles can work together to actually make sounds on yourcomputer.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/inheritance-derivative-songwriting\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Synthesizing Composition With Delegation","link":"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/","pubDate":"Sat, 05 Dec 2020 14:00:55 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li><strong>Synthesizing Composition With Delegation<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi To Play Music With Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together To Play Music<\/a><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>Any application will be comprised of multiple components - in Object-Orientedlanguages, typically classes. Sometimes these classes even work together!External users of one of these classes may not know that behind the scenes thereare more classes working together, nor do they care. The public API does whatthey need it to, and anything else is an implementation detail. However, keepingthe specialization of these different classes apart, but using them together,is beneficial.<\/p><p>To demonstrate using composition to model a complex system and using delegationin that composition, we will explore how a synthesizer can handle memorymanagement to store presets of sounds. This example comes from my <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a>talk about Ruby&rsquo;s<a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> module.<\/p><h2 id=\"noise-reduction\">Noise Reduction <a href=\"#noise-reduction\">\ud83d\udd17<\/a><\/h2><p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Synthesizer\">synthesizer<\/a> is an instrumentcapable of producing a wide array of sounds. A collection of sounds and effects are known as a patch.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Patch<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:sound<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:effect<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:filter<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:oscillator<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>You can save these patches on the synthesizer&rsquo;s memory and recall them later foreasy access.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span>patch <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Patch<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span>synth <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Synthesizer<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>synth<span style=\"color:#f92672\">.<\/span>save_patch(<span style=\"color:#e6db74\">location<\/span>: <span style=\"color:#e6db74\">:b1<\/span>, <span style=\"color:#e6db74\">patch<\/span>: patch)<\/span><\/span><span style=\"display:flex;\"><span>synth<span style=\"color:#f92672\">.<\/span>set_patch(patch)<\/span><\/span><span style=\"display:flex;\"><span>synth<span style=\"color:#f92672\">.<\/span>play_key(<span style=\"color:#e6db74\">note<\/span>: <span style=\"color:#e6db74\">:a<\/span>, <span style=\"color:#e6db74\">duration<\/span>: <span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><\/code><\/pre><\/div><h2 id=\"save-you-the-trouble\">Save You the Trouble <a href=\"#save-you-the-trouble\">\ud83d\udd17<\/a><\/h2><p>Much like the actual instrument is comprised of various subcomponents, our<code>Synthesizer<\/code> is made up of various classes that specialize in its area ofexpertise.<\/p><p>For example, our synthesizer above doesn&rsquo;t know how to save a patch to itsonboard memory. It relies on its patch memory to handle that.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Synthesizer<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">save_patch<\/span>(<span style=\"color:#e6db74\">location<\/span>:, <span style=\"color:#e6db74\">patch<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> @patch_memory<span style=\"color:#f92672\">.<\/span>write(<span style=\"color:#e6db74\">location<\/span>: location, <span style=\"color:#e6db74\">patch<\/span>: patch)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>All the synthesizer itself knows is what <em>message<\/em> to send to the memory tohave it do that. The synthesizer is <strong>delegating<\/strong> the responsibility of storingthese patches to the patch memory instance.<\/p><p>Anyone playing the synthesizer does not need to be concerned with how it&rsquo;sstoring these patches, just that it&rsquo;s doing it. Anyone using our synthesizerclass isn&rsquo;t aware that there is a separate patch memory class that thesynthesizer is using.<\/p><p>At the same time, our synthesizer doesn&rsquo;t know directly how to access its memory.It relies on the <code>PatchMemory<\/code> class for that, and delegates any responsibilityrelated to memory management to that class. As Sandi Metz describes in<a href=\"https:\/\/www.poodr.com\/\">Practical Object-Oriented Design In Ruby<\/a>, a synthesizer<em>has a<\/em> patch memory, as it <em>has a<\/em> series of other parts, and those arecomposed together to deliver all the functionality that a synthesizerprovides.<\/p><h2 id=\"key-benefits-to-delegation\">Key Benefits to Delegation <a href=\"#key-benefits-to-delegation\">\ud83d\udd17<\/a><\/h2><p>Delegation provides a few important drivers that make it easier to wranglecomplex systems.<\/p><h3 id=\"specialization\">Specialization <a href=\"#specialization\">\ud83d\udd17<\/a><\/h3><p>Our patch memory component is solely focused on interfacing with the onboardmemory of the instrument, which is where it saves and recalls stored sounds.Its tests can dig into all of the edge cases and minutiae that need to beaccounted for. The implementation can make very specific decisions so that itis extremely performant without other areas of the system needing to worryabout that.<\/p><p>A synthesizer itself is a complex system. The memory management is only onesmall part of it. The strength and value-add of our <code>Synthesizer<\/code> class is inorganizing all of these components together, knowing the right messages to passto them, with a public API that doesn&rsquo;t require intimate knowledge of all thosedetails. If the internals of our <code>Synthesizer<\/code> class handled all of thisresponsibility itself, it would quickly become unwieldy, difficult to navigate,hard to read, a challenge to troubleshoot, a burden to test, and feared whenchanges are required.<\/p><h3 id=\"flexibility--reuse\">Flexibility &amp; Reuse <a href=\"#flexibility--reuse\">\ud83d\udd17<\/a><\/h3><p>In reality, there are many different kinds of synthesizer, all of which havedifferent capabilities. Some may be able to store 1,000 different patches onboard. Others may only have capacity for four. Still more may have expandablememory, where you can plug in a USB device for nearly infinite storage.<\/p><p>Rather than needing to create entirely different synthesizer classes to handleany of these scenarios, instead we only need to model those differences in patchmemory classes. Our synthesizer can then use any of those and still maintain therest of its functionality, without needing to duplicate it across differentclasses.<\/p><p>In this <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/instruments\/synthesizer.rb#L41-L47\">example<\/a>,our synthesizer changes its memory capabilities based on the brand that it is.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize_memory<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">if<\/span> @brand <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">:moog<\/span><\/span><\/span><span style=\"display:flex;\"><span> @patch_memory <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">MoogPatchMemory<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">elsif<\/span> @brand <span style=\"color:#f92672\">==<\/span> <span style=\"color:#e6db74\">:nord<\/span><\/span><\/span><span style=\"display:flex;\"><span> @patch_memory <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">NordPatchMemory<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Thanks to <a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">duck typing<\/a>, as long as these patchmemory classes respond to the same messages, our <code>Synthesizer<\/code> class can useeither of them interchangeably.<\/p><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Composing classes together allows us to create a fully-functional system. Aclass that uses another class to handle a request or responsibility isdelegating that duty to the helper class. Delegation can encapsulate theknowledge of different specialties for code organization without externalconsumers needing to know or care about that implementation detail. Delegatingresponsibility to different classes can also make it easier for the system tochange, making it more likely to promote code reuse.<\/p><p>Next we&rsquo;re going to play one of the greatest hits in software design:<a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">inheritance<\/a>.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/synthesizing-composition-with-delegation\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Shedding a Light on Duck Typing","link":"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/","pubDate":"Thu, 03 Dec 2020 12:10:31 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li><strong>Shedding a Light on Duck Typing<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi To Play Music With Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together To Play Music<\/a><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>Duck typing is commonly used by Rubyists and other users of dynamic languages.We&rsquo;ll demonstrate duck typing by helping a concert lighting team set up thelighting for a band, which comes from my <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a> talk about Ruby&rsquo;s<a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> module.<\/p><h2 id=\"stage-design\">Stage Design <a href=\"#stage-design\">\ud83d\udd17<\/a><\/h2><p>To light the stage for our concert, we have a wide range of lights touse.<\/p><p>We have our trusty can, or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stage_lighting_instrument#PAR_lights\">PAR<\/a>, lights.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">CanLight<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(<span style=\"color:#e6db74\">color<\/span>:, <span style=\"color:#e6db74\">effect<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We have <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stage_lighting_instrument#Spotlights\">spotlights<\/a> tasked on each band member.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Spotlight<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(<span style=\"color:#e6db74\">color<\/span>:, <span style=\"color:#e6db74\">effect<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We have fancy <a href=\"https:\/\/en.wikipedia.org\/wiki\/Intelligent_lighting\">moving lights<\/a> for versatile coverage across the stage.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">MovingLight<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(<span style=\"color:#e6db74\">color<\/span>:, <span style=\"color:#e6db74\">effect<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>We even have a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Beam_projector\">beam projector<\/a>for a more powerful spotlight effect.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">BeamProjector<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(<span style=\"color:#e6db74\">color<\/span>:, <span style=\"color:#e6db74\">effect<\/span>:)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">...<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>What any of these lights do isn&rsquo;t important here. What is key to notice is thatyou operate them all by calling the <code>trigger<\/code> method.<\/p><h2 id=\"a-light-touch\">A Light Touch <a href=\"#a-light-touch\">\ud83d\udd17<\/a><\/h2><p>The stage lighting technicians, just like the band, are performers in theconcert. For every single note of every single song, they need to make sure thatthe visual aesthetic of the stage is set <em>just so<\/em>.<\/p><p>All of these lights are managed by a central controller, from which they canpower on all the lights in preparation for a show.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LightingController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @powered_lights <span style=\"color:#f92672\">=<\/span> {}<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">turn_on_lights<\/span><\/span><\/span><span style=\"display:flex;\"><span> @powered_lights<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:beam_projector<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">BeamProjector<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> @powered_lights<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:can<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">CanLight<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> @powered_lights<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:moving_light<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">MovingLight<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> @powered_lights<span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:spotlight<\/span><span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Spotlight<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>As I mentioned, for every note of every song, they need to make sure the lightslook exactly as they&rsquo;re supposed to. This is tracked as the lighting&rsquo;scomposition.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LightingComposition<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:light_name<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:color<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">attr_reader<\/span> <span style=\"color:#e6db74\">:effect<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"ducking-into-lights-on-stage\">Ducking into Lights on Stage <a href=\"#ducking-into-lights-on-stage\">\ud83d\udd17<\/a><\/h2><p>As the band is playing the show, the lighting technicians follow note-for-noteand need to apply the composition.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Song<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">play<\/span><\/span><\/span><span style=\"display:flex;\"><span> @notes<span style=\"color:#f92672\">.<\/span>map <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>note<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">=<\/span> <span style=\"color:#f92672\">[]<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @guitar<span style=\"color:#f92672\">.<\/span>play(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @vocal<span style=\"color:#f92672\">.<\/span>sing(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @drum<span style=\"color:#f92672\">.<\/span>hit(note) }<\/span><\/span><span style=\"display:flex;\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @keyboardist<span style=\"color:#f92672\">.<\/span>program(note) }<\/span><\/span><span style=\"display:flex; background-color:#3c3d38\"><span> composition <span style=\"color:#f92672\">&lt;&lt;<\/span> <span style=\"color:#66d9ef\">Thread<\/span><span style=\"color:#f92672\">.<\/span>new { @lighting<span style=\"color:#f92672\">.<\/span>set_lighting(note) }<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> composition<span style=\"color:#f92672\">.<\/span>map(<span style=\"color:#f92672\">&amp;<\/span><span style=\"color:#e6db74\">:value<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>Because each of our different lights respond to the same message (<code>trigger<\/code>)with the same signature, the lighting controller doesn&rsquo;t need to care, or evenknow, about which light it&rsquo;s operating. All it knows is that it needs to sendit the trigger signal and apply the required composition.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LightingController<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">set_lighting<\/span>(note)<\/span><\/span><span style=\"display:flex;\"><span> lighting_composition <span style=\"color:#f92672\">=<\/span> note<span style=\"color:#f92672\">.<\/span>lighting<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> trigger(@powered_lights<span style=\"color:#f92672\">[<\/span>lighting_composition<span style=\"color:#f92672\">.<\/span>light_name<span style=\"color:#f92672\">]<\/span>, lighting_composition)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(light, composition)<\/span><\/span><span style=\"display:flex;\"><span> light<span style=\"color:#f92672\">.<\/span>trigger(<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">color<\/span>: composition<span style=\"color:#f92672\">.<\/span>color,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">effect<\/span>: composition<span style=\"color:#f92672\">.<\/span>effect,<\/span><\/span><span style=\"display:flex;\"><span> )<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>The <code>LightingController<\/code>&rsquo;s <code>trigger<\/code> method is taking advantage of duck typing.Ruby doesn&rsquo;t care what kind of object it&rsquo;s calling in its <code>light<\/code> argument. Allthat matters is that it responds to <code>trigger<\/code>. We also used duck typing when wediscussed <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency injection<\/a>. Our guitardidn&rsquo;t care how the amplifier made sound, or even if it did make sound. All thatmatters at runtime to satisfy Ruby is that the object we pass in responds to<code>play<\/code> and accepts an argument.<\/p><h2 id=\"static-lighting\">Static Lighting <a href=\"#static-lighting\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;re more familiar with static languages or different typing systems, andyou need to define common behavior for what a set of classes do, you may befamiliar with an interface. For example, let&rsquo;s use Java to define an interfacefor our lights.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-java\" data-lang=\"java\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">interface<\/span> <span style=\"color:#a6e22e\">Light<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">void<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(Color color, LightingEffect effect)<\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>Each of our lights would then implement this interface, defining their ownimplementation of what they do when the light is triggered.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-java\" data-lang=\"java\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Spotlight<\/span> <span style=\"color:#66d9ef\">implements<\/span> Light {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e22e\">@Override<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">public<\/span> <span style=\"color:#66d9ef\">void<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(Color color, LightingEffect effect) {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#75715e\">\/\/ Turn the light on or off<\/span><\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>We can now set our <code>LightingController<\/code>&rsquo;s <code>trigger<\/code> method to accept any kind oflight.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-java\" data-lang=\"java\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">LightingController<\/span> {<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">public<\/span> <span style=\"color:#66d9ef\">void<\/span> <span style=\"color:#a6e22e\">trigger<\/span>(Light light, LightingComposition composition) {<\/span><\/span><span style=\"display:flex;\"><span> light.<span style=\"color:#a6e22e\">trigger<\/span>(composition.<span style=\"color:#a6e22e\">color<\/span>, composition.<span style=\"color:#a6e22e\">effect<\/span>);<\/span><\/span><span style=\"display:flex;\"><span> }<\/span><\/span><span style=\"display:flex;\"><span>}<\/span><\/span><\/code><\/pre><\/div><p>This satisfies Java&rsquo;s type system, because anything that implementsthe <code>Light<\/code> interface is required to respond to the <code>trigger<\/code> method acceptingthose types of arguments.<\/p><p>Because of duck typing in Ruby, defining this contract and enforcing it isunnecessary. However, Ruby 3 will be <a href=\"https:\/\/www.ruby-lang.org\/en\/news\/2020\/09\/25\/ruby-3-0-0-preview1-released\/\">shipping<\/a> with a way to define typesignatures, called <a href=\"https:\/\/github.com\/ruby\/rbs\">RBS<\/a>. RBS includes a mechanismto define interfaces, which you can read more about <a href=\"https:\/\/developer.squareup.com\/blog\/the-state-of-ruby-3-typing\/\">here<\/a>.<\/p><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Duck typing is a core design feature of Ruby and other dynamic languages;however, it does require a degree of trust. Because everything is determined atruntime, there&rsquo;s nothing stopping you from passing in an object to a method thatdoesn&rsquo;t respond to the methods it needs to. That will generate anexception, but that may be too late to get that feedback. However, to manyRubyists, the flexibility this approach provides often outweighs the cost.<\/p><p>If you&rsquo;re coming from a static typing system, or desiring more direction orenforcement about what&rsquo;s expected to be provided as an argument, theninvestigate defining interface types with RBS, which will be part of Ruby 3.<\/p><p>Our concert series continues with a discussion on <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">composition and delegation<\/a>.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/shedding-light-on-duck-typing\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Dependency Injection: Plug In","link":"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/","pubDate":"Tue, 24 Nov 2020 14:10:31 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/","description":"<h2 id=\"ruby-software-design-concert-series\">Ruby Software Design Concert Series <a href=\"#ruby-software-design-concert-series\">\ud83d\udd17<\/a><\/h2><ol><li><strong>Dependency Injection: Plug In<\/strong><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi To Play Music With Ruby<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together To Play Music<\/a><\/li><\/ol><h2 id=\"setting-the-stage\">Setting the Stage <a href=\"#setting-the-stage\">\ud83d\udd17<\/a><\/h2><p>Dependency injection is a fancy term. It <strong>sounds<\/strong> intimidating. The purpose ofthis post is to explain what dependency injection is, how to use it, and why itcan be beneficial. To illustrate, let&rsquo;s talk about playing a guitar in aconcert, which comes from my <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">RubyConf 2020<\/a>talk about Ruby&rsquo;s <a href=\"https:\/\/docs.ruby-lang.org\/en\/master\/Coverage.html\">Coverage<\/a> module.<\/p><h2 id=\"sound-check\">Sound Check <a href=\"#sound-check\">\ud83d\udd17<\/a><\/h2><p>A guitarist in a band uses an amplifier when playing a live concert.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span><\/span><\/span><span style=\"display:flex;\"><span> @amplifier <span style=\"color:#f92672\">=<\/span> <span style=\"color:#66d9ef\">Amplifier<\/span><span style=\"color:#f92672\">.<\/span>new<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>When the guitar is played the sound travels through the amplifier, so theaudience can hear the notes being played.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">strum<\/span>(chord)<\/span><\/span><span style=\"display:flex;\"><span> chord<span style=\"color:#f92672\">.<\/span>phrasing<span style=\"color:#f92672\">.<\/span>each <span style=\"color:#66d9ef\">do<\/span> <span style=\"color:#f92672\">|<\/span>string_sound<span style=\"color:#f92672\">|<\/span><\/span><\/span><span style=\"display:flex;\"><span> @amplifier<span style=\"color:#f92672\">.<\/span>play(string_sound<span style=\"color:#f92672\">.<\/span>amp_value)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>You can play a great show with this setup! Your guitar uses the amplifier itdefines, and all is well. Until&hellip;<\/p><h2 id=\"wall-of-sound\">Wall of Sound <a href=\"#wall-of-sound\">\ud83d\udd17<\/a><\/h2><p>Some guitarists experiment with gear - a lot. Different amplifiers are going tomake different sounds. However, we&rsquo;ve made it very difficult for our guitar tobe plugged in to different amplifiers.<\/p><p>Right now, our dependence on the amplifier class to play the sound from theguitar is hard-coded in the <code>Guitar<\/code> class. The initializer sets up an explicitdependency with the <code>Amplifier<\/code> class.<\/p><p>If we want to plug the guitar into a <code>LouderAmplifier<\/code>, we can&rsquo;t do that withoutchanging our <code>Guitar<\/code> class. Every different amplifier will require a change toour <code>Guitar<\/code> class.<\/p><h2 id=\"plug-and-play\">Plug and Play <a href=\"#plug-and-play\">\ud83d\udd17<\/a><\/h2><p>We can resolve this limitation by instead passing in the amplifier that&rsquo;ll beused with the guitar when we make a new guitar.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">class<\/span> <span style=\"color:#a6e22e\">Guitar<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">def<\/span> <span style=\"color:#a6e22e\">initialize<\/span>(amplifier)<\/span><\/span><span style=\"display:flex;\"><span> @amplifier <span style=\"color:#f92672\">=<\/span> amplifier<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">end<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">end<\/span><\/span><\/span><\/code><\/pre><\/div><p>With this small change, our <code>Guitar<\/code> can work with any amplifier that respondsto the <code>play<\/code> method. Rather than being coupled to the <code>Amplifier<\/code> class, werequire that any users of the <code>Guitar<\/code> class instead explicitly pass in thiscollaborating class. This is a form of dependency injection, specifically<a href=\"https:\/\/martinfowler.com\/articles\/injection.html#ConstructorInjectionWithPicocontainer\">Constructor Injection<\/a>.<\/p><h2 id=\"sound-engineering\">Sound Engineering <a href=\"#sound-engineering\">\ud83d\udd17<\/a><\/h2><p>Now that we&rsquo;ve seen an example of what dependency injection is, let&rsquo;s discusswhy we would want to use it.<\/p><h3 id=\"flexibility\">Flexibility <a href=\"#flexibility\">\ud83d\udd17<\/a><\/h3><p>This is the motivation described in the example above. By removing thehard-coded dependency as an implementation detail of our class, we can insteaduse any dependency desired, as long as it responds to the methods that we needto use within the class. For us, this means that guitars can use any amplifierthey&rsquo;d like; the guitarist isn&rsquo;t limited to the amp they had when first buyingthe guitar.<\/p><h3 id=\"testing\">Testing <a href=\"#testing\">\ud83d\udd17<\/a><\/h3><p>Testing may be the first situation where the value of this flexibility can beappreciated. Tests are the first consumers of your implementation, and it&rsquo;simportant to listen to the implicit feedback they give you. If a class or amethod is hard to test, it very likely will be hard to use - or at least complexto understand.<\/p><p>In reality, the difficulty in testing the <code>Guitar<\/code> <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/instruments\/guitar.rb\">class<\/a>is what led to the decision to inject the amplifier in. That&rsquo;s because the<code>Amplifier<\/code> <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/amplifier.rb\">class<\/a>is essentially a wrapper around <a href=\"https:\/\/sonic-pi.net\/\">Sonic Pi<\/a>. Sonic Pidescribes itself as a &ldquo;code-based music creation and performance tool&rdquo;, soplaying the guitar with this amplifier will actually play a sound on yourcomputer.<\/p><p>As exciting as that is, I don&rsquo;t want to have Sonic Pi running just to execute mytests. And even if I did, I don&rsquo;t need to hear the sound it would generate everytime I run my tests. And so, I created a separate amp for testing: a<code>PracticeAmplifier<\/code>. What does that amp do? <a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\/blob\/09e7b72b38dac09d4968afe1468eda53caaf294c\/lib\/ruby_cover_band\/practice_amplifier.rb\">Absolutely nothing<\/a>!And that&rsquo;s perfect for my unit tests. They&rsquo;re not concerned with the sound theamplifier makes when playing the guitar. They&rsquo;re interested in exercising thelogic that&rsquo;s within the <code>Guitar<\/code> class only.<\/p><p>More generally, maybe your class is collaborating with another class that makesan API call or performs file I\/O. You don&rsquo;t want to have to execute or mock outthose actions in your class&rsquo;s tests - it&rsquo;s the collaborator&rsquo;s tests that shouldbe concerned with that. You can instead pass in another class that doesn&rsquo;t dothose things, providing speedy and relevant feedback in your tests.<\/p><h3 id=\"complexity-identification\">Complexity Identification <a href=\"#complexity-identification\">\ud83d\udd17<\/a><\/h3><p>The responsibility of systems tends to grow over time. This is true not only foryour entire application, but the different components of it, down to theindividual classes or methods. As functionality continues to get added toclasses, you may need to add in more and more collaborators. If each of thesechanges are made piecemeal over time, it can be difficult to step back andrealize not only how coupled a class is to other classes, but how <strong>many<\/strong>classes it&rsquo;s coupled to.<\/p><p>Injecting dependencies explicitly makes it more clear <em>what<\/em> this class isdependent on, and <em>how many<\/em> things. As the list of things you need to pass in toa constructor or a method grows to support new features, it can serveas a proxy to gauge how complexity within the class or method is growing. Thismay exert more natural pressure to identify different abstractions orrefactorings to implement.<\/p><h2 id=\"rock-on\">Rock On <a href=\"#rock-on\">\ud83d\udd17<\/a><\/h2><p>Ruby&rsquo;s inherent flexibility can make dependency injection a less-likely tool toreach for, particularly if <a href=\"https:\/\/dhh.dk\/2012\/dependency-injection-is-not-a-virtue.html\">testing<\/a> is when you would notice that pain initially, given the <a href=\"https:\/\/www.youtube.com\/watch?v=iEfpAp2sqiw\">tools<\/a> at ourdisposal to make testing interactions with dependencies easier.<\/p><p>Dependency injection is also a daunting term that often carries the assumptionthat you need a heavyweight <a href=\"https:\/\/en.wikipedia.org\/wiki\/Dependency_injection#Dependency_injection_frameworks\">framework<\/a> to implement it. However, if you can pass in an object as an argument to an initializer (constructor) or even an individual method - congratulations, you&rsquo;ve injected a dependency!<\/p><p>Using dependency injection can lead to less tightly-coupled code, whichallows for more flexibility in collaborating with others, reduces the burden oftesting, and makes it more clear when classes are growing to the point wheretheir current design needs to be reconsidered.<\/p><p>The next post in our series explains using <a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">duck typing<\/a> in ruby.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/dependency-injection-plug-in\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Explain Like I'm Five","link":"https:\/\/kevinjmurphy.com\/posts\/explain-like-im-five\/","pubDate":"Wed, 11 Nov 2020 15:38:10 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/explain-like-im-five\/","description":"<h2 id=\"explaining-myself\"><code>EXPLAIN<\/code>ing Myself <a href=\"#explaining-myself\">\ud83d\udd17<\/a><\/h2><p>Running <code>EXPLAIN<\/code> to show the execution plan for different database queries canhelp you understand why the performance of a particular database interactionis the way it is, and how you might be able to improve it. The purpose of thispost is not to interpret and understand the results of an execution plan.Rather, we&rsquo;ll learn five different ways you can get this information from yourRails app.<\/p><p>Some of these examples are PostgreSQL-specific.<\/p><h2 id=\"1-activerecords-explain-method\">1. ActiveRecord&rsquo;s <code>explain<\/code> Method <a href=\"#1-activerecords-explain-method\">\ud83d\udd17<\/a><\/h2><p>Rails already has the <code>explain<\/code> method <a href=\"https:\/\/apidock.com\/rails\/ActiveRecord\/Relation\/explain\">built into<\/a> ActiveRecord for you to use.You can add <code>explain<\/code> to any ActiveRecord relation, and you&rsquo;ll receive theexecution plan.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>explain<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">EXPLAIN<\/span> <span style=\"color:#66d9ef\">for<\/span>: <span style=\"color:#66d9ef\">SELECT<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span><span style=\"color:#f92672\">.<\/span>* <span style=\"color:#66d9ef\">FROM<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span> <span style=\"color:#66d9ef\">WHERE<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#e6db74\">&#34;email&#34;<\/span> <span style=\"color:#f92672\">=<\/span> $1 <span style=\"color:#f92672\">[[<\/span><span style=\"color:#e6db74\">&#34;email&#34;<\/span>, <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span><span style=\"color:#f92672\">]]<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">QUERY<\/span> <span style=\"color:#66d9ef\">PLAN<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">-------------------------------------------------------<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Seq<\/span> <span style=\"color:#66d9ef\">Scan<\/span> on users (cost<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">00<\/span><span style=\"color:#f92672\">..<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">71<\/span> rows<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">1<\/span> width<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">340<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">Filter<\/span>: ((email)<span style=\"color:#f92672\">::<\/span>text <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#39;test@example.com&#39;<\/span><span style=\"color:#f92672\">::<\/span>text)<\/span><\/span><span style=\"display:flex;\"><span>(<span style=\"color:#ae81ff\">2<\/span> rows)<\/span><\/span><\/code><\/pre><\/div><p>This gives us a great starting point, and works across various databases.However, if you want some additional features, like <a href=\"https:\/\/www.postgresql.org\/docs\/current\/sql-explain.html\">running<\/a> <code>EXPLAIN ANALYZE<\/code>, you&rsquo;ll need to look elsewhere. Unless you&rsquo;re using ActiveRecord 7.1 or higher, where it&rsquo;s <a href=\"https:\/\/www.bigbinary.com\/blog\/rails-7-1-adds-options-to-activerecord-relation-explain#analyze\">now available<\/a>.<\/p><h2 id=\"2-interpolating-a-query-in-an-activerecord-connection\">2. Interpolating a Query in an ActiveRecord Connection <a href=\"#2-interpolating-a-query-in-an-activerecord-connection\">\ud83d\udd17<\/a><\/h2><p>You can fall back to creating your own SQL statement and passing that intoActiveRecord&rsquo;s <code>execute<\/code> method. However, you probably don&rsquo;t want to go throughthe error-prone and arduous effort of hand-writing the SQL query you just wroteusing ActiveRecord&rsquo;s syntax.<\/p><p>Luckily, you don&rsquo;t have to! You can convert your ActiveRecord query to a string with<code>.to_sql<\/code>, and add that into a string you provide to <code>execute<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">ActiveRecord<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Base<\/span><span style=\"color:#f92672\">.<\/span>connection<span style=\"color:#f92672\">.<\/span>execute(<span style=\"color:#e6db74\">&#34;EXPLAIN <\/span><span style=\"color:#e6db74\">#{<\/span><span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_sql<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>values<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[[<\/span><span style=\"color:#e6db74\">&#34;Seq Scan on users (cost=0.00..2.71 rows=1 width=340)&#34;<\/span><span style=\"color:#f92672\">]<\/span>, <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34; Filter: ((email)::text = &#39;test@example.com&#39;::text)&#34;<\/span><span style=\"color:#f92672\">]]<\/span><\/span><\/span><\/code><\/pre><\/div><p>This itself isn&rsquo;t much of a win at all over using ActiveRecord&rsquo;s <code>explain<\/code>method. It&rsquo;s longer, you&rsquo;ve got to remember to grab the <code>values<\/code> from the<code>execute<\/code> results, and the output isn&rsquo;t as nicely formatted. However, becausethis is &ldquo;just SQL&rdquo; that you&rsquo;re running in <code>execute<\/code>, you can use any featuresyour database engine of choice provides, like <code>EXPLAIN ANALYZE<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">ActiveRecord<\/span><span style=\"color:#f92672\">::<\/span><span style=\"color:#66d9ef\">Base<\/span><span style=\"color:#f92672\">.<\/span>connection<span style=\"color:#f92672\">.<\/span>execute(<span style=\"color:#e6db74\">&#34;EXPLAIN ANALYZE <\/span><span style=\"color:#e6db74\">#{<\/span><span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>to_sql<span style=\"color:#e6db74\">}<\/span><span style=\"color:#e6db74\">&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>values<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[[<\/span><span style=\"color:#e6db74\">&#34;Seq Scan on users (cost=0.00..2.71 rows=1 width=340) (actual time=0.184..0.233 rows=0 loops=1)&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34; Filter: ((email)::text = &#39;test@example.com&#39;::text)&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34; Rows Removed by Filter: 57&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Planning time: 0.185 ms&#34;<\/span><span style=\"color:#f92672\">]<\/span>,<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Execution time: 0.472 ms&#34;<\/span><span style=\"color:#f92672\">]]<\/span><\/span><\/span><\/code><\/pre><\/div><p>Thanks to <a href=\"https:\/\/github.com\/mlodato517\">Mark Lodato<\/a> for this recommendation.<\/p><h2 id=\"3-the-activerecord-explain-analyze-gem\">3. The activerecord-explain-analyze Gem <a href=\"#3-the-activerecord-explain-analyze-gem\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;re willing to take on a dependency to get some additional explanatory power,are using ActiveRecord 4 through 6, and use PostgresSQL, thenyou can reach for the <a href=\"https:\/\/github.com\/6\/activerecord-explain-analyze\">activerecord-explain-analyze<\/a> gem.<\/p><p>Now you can specify the output formatting of your <code>EXPLAIN<\/code> results, and call<code>EXPLAIN ANALYZE<\/code>:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>explain(<span style=\"color:#e6db74\">analyze<\/span>: <span style=\"color:#66d9ef\">true<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#66d9ef\">EXPLAIN<\/span> <span style=\"color:#66d9ef\">for<\/span>: <span style=\"color:#66d9ef\">SELECT<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span><span style=\"color:#f92672\">.<\/span>* <span style=\"color:#66d9ef\">FROM<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span> <span style=\"color:#66d9ef\">WHERE<\/span> <span style=\"color:#e6db74\">&#34;users&#34;<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#e6db74\">&#34;email&#34;<\/span> <span style=\"color:#f92672\">=<\/span> $1<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Seq<\/span> <span style=\"color:#66d9ef\">Scan<\/span> on <span style=\"color:#66d9ef\">public<\/span><span style=\"color:#f92672\">.<\/span>users (cost<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">00<\/span><span style=\"color:#f92672\">..<\/span><span style=\"color:#ae81ff\">2<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">71<\/span> rows<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">1<\/span> width<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">340<\/span>) (actual time<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">120<\/span><span style=\"color:#f92672\">..<\/span><span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">128<\/span> rows<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">0<\/span> loops<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">1<\/span>)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">Output<\/span>: id, email, sign_in_count, current_sign_in_at, last_sign_in_at, current_sign_in_ip, last_sign_in_ip, created_at, updated_at, time_zone, first_name, last_name, role, applicant_id, centrify_uuid, display_name, uuid, login_authorized, invite_id, legacy_identifier, disabled_at, invite_sent_at, password_last_changed_at, deprovisioning_reason<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">Filter<\/span>: ((users<span style=\"color:#f92672\">.<\/span>email)<span style=\"color:#f92672\">::<\/span>text <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#39;test@example.com&#39;<\/span><span style=\"color:#f92672\">::<\/span>text)<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#66d9ef\">Rows<\/span> <span style=\"color:#66d9ef\">Removed<\/span> by <span style=\"color:#e6db74\">Filter<\/span>: <span style=\"color:#ae81ff\">57<\/span><\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#e6db74\">Buffers<\/span>: shared hit<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">2<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Planning<\/span> <span style=\"color:#e6db74\">time<\/span>: <span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">277<\/span> ms<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">Execution<\/span> <span style=\"color:#e6db74\">time<\/span>: <span style=\"color:#ae81ff\">0<\/span><span style=\"color:#f92672\">.<\/span><span style=\"color:#ae81ff\">183<\/span> ms<\/span><\/span><\/code><\/pre><\/div><h2 id=\"4-the-pg-eyeballs-gem\">4. The pg-eyeballs Gem <a href=\"#4-the-pg-eyeballs-gem\">\ud83d\udd17<\/a><\/h2><p><a href=\"https:\/\/github.com\/bradurani\/pg-eyeballs\">pg-eyeballs<\/a> is another gem that&rsquo;s PostgreSQL-specific, and provides additionalfunctionality that ActiveRecord&rsquo;s <code>explain<\/code> method does not currently.<\/p><p>Our sought-after <code>EXPLAIN ANALYZE<\/code> is one of many options you can request:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ruby\" data-lang=\"ruby\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">&gt;<\/span> <span style=\"color:#66d9ef\">User<\/span><span style=\"color:#f92672\">.<\/span>where(<span style=\"color:#e6db74\">email<\/span>: <span style=\"color:#e6db74\">&#34;test@example.com&#34;<\/span>)<span style=\"color:#f92672\">.<\/span>eyeballs<span style=\"color:#f92672\">.<\/span>explain(<span style=\"color:#e6db74\">options<\/span>: <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">:analyze<\/span><span style=\"color:#f92672\">]<\/span>)<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">=&gt;<\/span> <span style=\"color:#f92672\">[<\/span><span style=\"color:#e6db74\">&#34;Seq Scan on users (cost=0.00..2.71 rows=1 width=340) (actual time=0.028..0.036 rows=0 loops=1)<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\"> Filter: ((email)::text = &#39;test@example.com&#39;::text)<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\"> Rows Removed by Filter: 57<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">Planning time: 0.087 ms<\/span><span style=\"color:#ae81ff\">\\n<\/span><span style=\"color:#e6db74\">Execution time: 0.084 ms&#34;<\/span><span style=\"color:#f92672\">]<\/span><\/span><\/span><\/code><\/pre><\/div><h2 id=\"5-terminal-cli-of-your-database\">5. Terminal CLI of Your Database <a href=\"#5-terminal-cli-of-your-database\">\ud83d\udd17<\/a><\/h2><p>All of these prior examples have been run from within a Rails process such as theRails console. However, we can skip Rails entirely and use our databasedirectly. In our case with PostgreSQL, we can use <code>psql<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span><span style=\"color:#75715e\"># psql -U postgres<\/span><\/span><\/span><\/code><\/pre><\/div><p>After connecting, we can list which databases exist with <code>\\l<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>postgres<span style=\"color:#f92672\">=<\/span><span style=\"color:#75715e\"># \\l<\/span><\/span><\/span><span style=\"display:flex;\"><span> List of databases<\/span><\/span><span style=\"display:flex;\"><span> Name | Owner | Encoding | Collate | Ctype | Access privileges<\/span><\/span><span style=\"display:flex;\"><span>-------------------------------------+----------+----------+------------+------------+-----------------------<\/span><\/span><span style=\"display:flex;\"><span> example_app_development | postgres | UTF8 | en_US.utf8 | en_US.utf8 |<\/span><\/span><\/code><\/pre><\/div><p>After finding the correct database, we can connect to it with <code>\\c<\/code>.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>postgres<span style=\"color:#f92672\">=<\/span><span style=\"color:#75715e\"># \\c example_app_development<\/span><\/span><\/span><\/code><\/pre><\/div><p>And then we can run any query we would like, including EXPLAIN:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>example_app_development<span style=\"color:#f92672\">=<\/span><span style=\"color:#75715e\"># EXPLAIN SELECT * FROM USERS WHERE EMAIL = &#39;test@example.com&#39;;<\/span><\/span><\/span><span style=\"display:flex;\"><span> QUERY PLAN<\/span><\/span><span style=\"display:flex;\"><span>-------------------------------------------------------<\/span><\/span><span style=\"display:flex;\"><span> Seq Scan on users <span style=\"color:#f92672\">(<\/span>cost<span style=\"color:#f92672\">=<\/span>0.00..2.71 rows<span style=\"color:#f92672\">=<\/span><span style=\"color:#ae81ff\">1<\/span> width<span style=\"color:#f92672\">=<\/span>340<span style=\"color:#f92672\">)<\/span><\/span><\/span><span style=\"display:flex;\"><span> Filter: <span style=\"color:#f92672\">((<\/span>email<span style=\"color:#f92672\">)<\/span>::text <span style=\"color:#f92672\">=<\/span> <span style=\"color:#e6db74\">&#39;test@example.com&#39;<\/span>::text<span style=\"color:#f92672\">)<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">(<\/span><span style=\"color:#ae81ff\">2<\/span> rows<span style=\"color:#f92672\">)<\/span><\/span><\/span><\/code><\/pre><\/div><p>Again, we have all the features available to us that our database enginesupports, so we can use <code>EXPLAIN ANALYZE<\/code> or any other functionality, withoutneeding it to be built into Rails. This gives us all the power our databaseprovides, but we lose the expressiveness of ActiveRecord&rsquo;s query API - orrather, we need to find the <code>.to_sql<\/code> representation of the query we&rsquo;reinterested in prior to using this.<\/p><h2 id=\"explaining-which-to-use\"><code>EXPLAIN<\/code>ing Which to Use <a href=\"#explaining-which-to-use\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;re interested in quickly getting an execution plan of an existingActiveRecord query, start with using ActiveRecord&rsquo;s <code>explain<\/code> method.<\/p><p>Should you need more functionality that your database engine provides, you can<code>execute<\/code> any query you would like to your database through ActiveRecord.<\/p><p>If you need that additional functionality, such as <code>EXPLAIN ANALYZE<\/code>, on aregular basis, consider taking on an additional dependency that&rsquo;ll provide thatfor you, such as <code>activerecord-explain-analyze<\/code> or <code>pg-eyeballs<\/code>.<\/p><p>Don&rsquo;t forget you can go directly to your database without using Rails as anintermediary.<\/p><p>I hope this has <code>EXPLAIN<\/code>ed a thing or two (or five) about ways to gather performanceinformation for your queries. Once you decide which method is right for you,good luck optimizing!<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/explain-like-im-five\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Enough Coverage To Beat The Band","link":"https:\/\/kevinjmurphy.com\/posts\/coverage\/","pubDate":"Mon, 19 Oct 2020 08:12:10 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/coverage\/","description":"<h2 id=\"thank-you\">Thank You! <a href=\"#thank-you\">\ud83d\udd17<\/a><\/h2><p>Thanks for checking out my talk! To keep in touch, you can join <a href=\"https:\/\/newsletter.kevinjmurphy.com\/\">my newsletter<\/a>or <a href=\"https:\/\/kevinjmurphy.com\/about\/#elsewhere-on-the-internet\">follow me<\/a> on the Internet.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/H4pBjWOMr_Y?si=LuO1nsqTJRCMOOIk\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><ul><li>Blue Ridge Ruby <a href=\"https:\/\/youtu.be\/H4pBjWOMr_Y\">Video<\/a><\/li><li>RubyConf <a href=\"https:\/\/youtu.be\/EyLO0EEm3BQ\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/enough-coverage-to-beat-the-band\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/ruby_cover_band\">Code Examples<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/rubys-got-you-covered\/\">Ruby&rsquo;s Got You Covered<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-more-coverage-in-ruby-3-2\/\">Evaluating More Coverage in Ruby 3.2<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">Dependency Injection: Plug In<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/shedding-light-on-duck-typing\/\">Shedding a Light on Duck Typing<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/synthesizing-composition-with-delegation\/\">Synthesizing Composition With Delegation<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/inheritance-derivative-songwriting\/\">Inheritance: Derivative Songwriting<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/using-sonic-pi-to-play-music-with-ruby\/\">Using Sonic Pi to Play Music With Ruby<\/a><\/li><li>Blog Post: <a href=\"https:\/\/kevinjmurphy.com\/posts\/stringing-code-together-to-play-music\/\">Stringing Code Together to Play Music<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/enough-coverage-to-beat-the-band-proposal\/\">Proposal<\/a><\/li><\/ul><h2 id=\"code-coverage-modes\">Code Coverage Modes <a href=\"#code-coverage-modes\">\ud83d\udd17<\/a><\/h2><p>Each mode answers a different question about the code that was run under coverage:<\/p><ul><li>Lines - how many times was each line executed?<\/li><li>Oneshot Lines - which lines were executed?<\/li><li>Methods - how many times was each method executed?<\/li><li>Branches - how many times was each conditional executed?<\/li><\/ul><h3 id=\"lines\">Lines <a href=\"#lines\">\ud83d\udd17<\/a><\/h3><p class=\"float-image coverage-emoji\">\ud83c\udfb8<\/p><p>This is the \u201cclassic\u201d implementation of providing coverage. Each relevant line, that is those that aren\u2019t things like empty lines or <code>end<\/code> statements, has a counter that is incremented each time the line is visited in code execution while coverage is running. At the conclusion, you will see how many times each line is executed.<\/p><h4 id=\"benefits\">Benefits <a href=\"#benefits\">\ud83d\udd17<\/a><\/h4><ul><li>This is the default mode for coverage.<\/li><li>Most of the time, this option will provide you with the information you want.<\/li><\/ul><h3 id=\"oneshot-lines\">Oneshot Lines <a href=\"#oneshot-lines\">\ud83d\udd17<\/a><\/h3><p class=\"float-image coverage-emoji\">\ud83c\udfb9<\/p><p>Similar to lines coverage, this also documents that a relevant line was executed while coverage was running. However, it\u2019s a binary report of whether it was executed or not. It will not tell you how often. This may be sufficient in many cases, and comes with the benefit of being more performant every subsequent time a particular line of code is executed under coverage.<\/p><h4 id=\"benefits-1\">Benefits <a href=\"#benefits-1\">\ud83d\udd17<\/a><\/h4><ul><li>Oneshot provides you with nothing more than if a line of application code is executed in a test suite.<\/li><li>As long as being constrained to knowing if something ran or not, and not knowing how often, is sufficient, oneshot lines coverage provides the same feedback as lines coverage with better performance.<\/li><\/ul><h3 id=\"methods\">Methods <a href=\"#methods\">\ud83d\udd17<\/a><\/h3><p class=\"float-image coverage-emoji\">\ud83d\udca1<\/p><p>Methods coverage brings the granularity of lines coverage up to a coarser grain. Rather than tracking individual lines, it\u2019s concerned with whether a particular method is executed. It can be a 10 line method where the first line is the only line ever executed. Methods coverage will still consider that as executed the same as a 20 line method where each line is executed.<\/p><h4 id=\"benefits-2\">Benefits <a href=\"#benefits-2\">\ud83d\udd17<\/a><\/h4><ul><li>This has a targeted focus to be able to answer a more specific question - is this method executed? - with easier to process feedback.<\/li><\/ul><h3 id=\"branches\">Branches <a href=\"#branches\">\ud83d\udd17<\/a><\/h3><p class=\"float-image coverage-emoji\">\ud83c\udfa4<\/p><p>Branches coverage tracks execution of different conditional paths and documents how often those different paths are run. The unique benefit that this provides over lines coverage is in conditionals that execute multiple code paths in a single line, such as ternary statements. You may have a part of that conditional that\u2019s never run or tested, but you wouldn&rsquo;t know that if you\u2019re relying on lines coverage alone.<\/p><h4 id=\"benefits-3\">Benefits <a href=\"#benefits-3\">\ud83d\udd17<\/a><\/h4><ul><li>It provides a different frame of reference than lines coverage, which ends up being either coarser or more granular than line coverage in different situations.<\/li><li>For conditionals that lay out multiple code paths on a single line, this provides feedback on their individual execution where lines coverage only considers whether any part of the line was run.<\/li><li>When interested in conditionals, and only conditionals, it has less noise than lines coverage.<\/li><\/ul><h2 id=\"gem-support\">Gem Support <a href=\"#gem-support\">\ud83d\udd17<\/a><\/h2><p>If you&rsquo;d like to leverage existing abstractions on top of ruby&rsquo;s coverage modulein your current applications, I recommend:<\/p><ul><li><a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov\">SimpleCov<\/a> for test coverage. Itnow supports <a href=\"https:\/\/github.com\/simplecov-ruby\/simplecov#branch-coverage-ruby--25\">branches<\/a> coverage!<\/li><li><a href=\"https:\/\/github.com\/danmayer\/coverband\">Coverband<\/a> for measuring coverage ofproduction code. Here&rsquo;s an <a href=\"https:\/\/github.com\/danmayer\/coverband\/blob\/43c5ac94febc7a961346b0e9408d829d4d2ef8ad\/test\/rails5_dummy\/config\/coverband.rb#L15\">example<\/a> on how to configure it to run using oneshot lines coverage.<\/li><\/ul><h2 id=\"supplemental-content\">Supplemental Content <a href=\"#supplemental-content\">\ud83d\udd17<\/a><\/h2><p>I have a series of blog posts using the live music examples I wrote for the talkto describe various software design principles. The first post is about<a href=\"https:\/\/kevinjmurphy.com\/posts\/dependency-injection-plug-in\/\">dependency injection<\/a>.You can see the full series <a href=\"https:\/\/kevinjmurphy.com\/tags\/software-design-concert-series\/\">here<\/a>.<\/p><h2 id=\"presentation-fonts\">Presentation Fonts <a href=\"#presentation-fonts\">\ud83d\udd17<\/a><\/h2><p>The main content of all my slides, for code examples and similar text, is Arial Narrow.<\/p><p>Slide titles and big statement fonts are The Fragile, available from <a href=\"https:\/\/www.theninhotline.com\/features\/fonts\/\">The NIN Hotline<\/a>.<\/p><p>Supplemental content, like my twitter handle and text pointing to areasexplaining what coverage output represents are The Wretched, also available from<a href=\"https:\/\/www.theninhotline.com\/features\/fonts\/\">The NIN Hotline<\/a>.<\/p><h2 id=\"presentation-photo-credits\">Presentation Photo Credits <a href=\"#presentation-photo-credits\">\ud83d\udd17<\/a><\/h2><p>All photos used in the presentation are from the band <a href=\"https:\/\/www.nin.com\/\">Nine Inch Nails<\/a>, and released ontheir <a href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/\">flickr<\/a> account with a <a href=\"https:\/\/creativecommons.org\/licenses\/by-nc-sa\/2.0\/\">Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Generic<\/a> license.<\/p><p>Below I&rsquo;ve embedded all the images used to link directly to their originalsource.<\/p><h3 id=\"introduction\">Introduction <a href=\"#introduction\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2738980841\/\" title=\"Nine Inch Nails Live @ Lollapalooza - Chicago, IL, 8.03.08\"><img src=\"https:\/\/live.staticflickr.com\/2222\/2738980841_dbc739b957_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Lollapalooza - Chicago, IL, 8.03.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3639150601\/\" title=\"Nine Inch Nails live @ PNC Bank Arts Center, Holmdel, NJ, 6.06.09\"><img src=\"https:\/\/live.staticflickr.com\/3606\/3639150601_409c17e154_c.jpg\" width=\"800\" height=\"534\" alt=\"Nine Inch Nails live @ PNC Bank Arts Center, Holmdel, NJ, 6.06.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><h3 id=\"lines-coverage\">Lines Coverage <a href=\"#lines-coverage\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3573650628\/\" title=\"Nine Inch Nails Live @ Shoreline Amphitheatre - Mountain View, CA, 5.22.09\"><img src=\"https:\/\/live.staticflickr.com\/2455\/3573650628_75ed822d85_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Shoreline Amphitheatre - Mountain View, CA, 5.22.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3573650014\/\" title=\"Nine Inch Nails Live @ Shoreline Amphitheatre - Mountain View, CA, 5.22.09\"><img src=\"https:\/\/live.staticflickr.com\/3340\/3573650014_cf942da814_c.jpg\" width=\"500\" height=\"750\" alt=\"Nine Inch Nails Live @ Shoreline Amphitheatre - Mountain View, CA, 5.22.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722222236\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/2841\/10722222236_c5e6dd1150_c.jpg\" width=\"533\" height=\"800\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3639160537\/\" title=\"Nine Inch Nails live @ Bonnaroo Festival in Manchester, TN, 6.13.09\"><img src=\"https:\/\/live.staticflickr.com\/3331\/3639160537_b3f8ea5158_c.jpg\" width=\"533\" height=\"800\" alt=\"Nine Inch Nails live @ Bonnaroo Festival in Manchester, TN, 6.13.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3572799825\/\" title=\"Nine Inch Nails Live @ Cricket Wireless Amphitheater - Chula Vista, CA, 5.16.09\"><img src=\"https:\/\/live.staticflickr.com\/2432\/3572799825_c091e87ff3_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Cricket Wireless Amphitheater - Chula Vista, CA, 5.16.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3639938714\/\" title=\"Nine Inch Nails live @ Starlight Theatre, Kansas City, MO, 5.27.09\"><img src=\"https:\/\/live.staticflickr.com\/3664\/3639938714_81c6079524_c.jpg\" width=\"800\" height=\"534\" alt=\"Nine Inch Nails live @ Starlight Theatre, Kansas City, MO, 5.27.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><h3 id=\"oneshot-lines-coverage\">Oneshot Lines Coverage <a href=\"#oneshot-lines-coverage\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3639113025\/\" title=\"Nine Inch Nails Live @ Sasquatch Festival in George, WA, on 5.24.09\"><img src=\"https:\/\/live.staticflickr.com\/2460\/3639113025_dee5235b96_c.jpg\" width=\"800\" height=\"534\" alt=\"Nine Inch Nails Live @ Sasquatch Festival in George, WA, on 5.24.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3032772287\/\" title=\"Nine Inch Nails Live @ Bell Centre - Montreal, QC, 11.12.08\"><img src=\"https:\/\/live.staticflickr.com\/3057\/3032772287_517c1d7b41_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Bell Centre - Montreal, QC, 11.12.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2739834768\" title=\"Nine Inch Nails Live @ Air Canada Centre - Toronto, ON, 8.05.08\"><img src=\"https:\/\/live.staticflickr.com\/3252\/2739834768_7d40ed685e_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Air Canada Centre - Toronto, ON, 8.05.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3573642602\" title=\"Nine Inch Nails Live @ Santa Barbara Bowl - Santa Barbara, CA, 5.21.09\"><img src=\"https:\/\/live.staticflickr.com\/3606\/3573642602_c343bd27b4_c.jpg\" width=\"800\" height=\"533\" alt=\"Nine Inch Nails Live @ Santa Barbara Bowl - Santa Barbara, CA, 5.21.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3212245282\" title=\"Nine Inch Nails Live @ Razzmatazz - Barcelona, Spain, 2.19.07\"><img src=\"https:\/\/live.staticflickr.com\/3463\/3212245282_e1fee55ee5_z.jpg\" width=\"640\" height=\"480\" alt=\"Nine Inch Nails Live @ Razzmatazz - Barcelona, Spain, 2.19.07\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3573605580\/\" title=\"Nine Inch Nails Live @ Cricket Wireless Amphitheater - Chula Vista, CA, 5.16.09\"><img src=\"https:\/\/live.staticflickr.com\/3601\/3573605580_2dafcc463c_z.jpg\" width=\"640\" height=\"427\" alt=\"Nine Inch Nails Live @ Cricket Wireless Amphitheater - Chula Vista, CA, 5.16.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3211397961\/\" title=\"Nine Inch Nails Live @ Olympia - Paris, France, 2.21.07\"><img src=\"https:\/\/live.staticflickr.com\/3481\/3211397961_2c19e04509_z.jpg\" width=\"640\" height=\"480\" alt=\"Nine Inch Nails Live @ Olympia - Paris, France, 2.21.07\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2827641506\/\" title=\"Nine Inch Nails Live @ Seagate Convention Center - Toledo, OH, 8.25.08\"><img src=\"https:\/\/live.staticflickr.com\/3148\/2827641506_d102315760_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Seagate Convention Center - Toledo, OH, 8.25.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2768600347\/\" title=\"Nine Inch Nails Live @ Gwinnett Arena - Duluth, GA, 8.13.08\"><img src=\"https:\/\/live.staticflickr.com\/3147\/2768600347_8a7059bf75_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Gwinnett Arena - Duluth, GA, 8.13.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2997174649\/\" title=\"Nine Inch Nails Live @ Arena Santiago - Santiago, Chile, 10.04.08\"><img src=\"https:\/\/live.staticflickr.com\/3276\/2997174649_c8067aa285_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Arena Santiago - Santiago, Chile, 10.04.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><h3 id=\"methods-coverage\">Methods Coverage <a href=\"#methods-coverage\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722216364\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/3723\/10722216364_e307c51d3b_z.jpg\" width=\"640\" height=\"427\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3572822083\/\" title=\"Nine Inch Nails Live @ The Pearl - Las Vegas, NV, 5.18.09\"><img src=\"https:\/\/live.staticflickr.com\/3298\/3572822083_88b2ccae93_z.jpg\" width=\"640\" height=\"427\" alt=\"Nine Inch Nails Live @ The Pearl - Las Vegas, NV, 5.18.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722275244\/\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/3694\/10722275244_34e8865f69_z.jpg\" width=\"640\" height=\"427\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722195315\/\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/7459\/10722195315_2e3a458395_z.jpg\" width=\"640\" height=\"427\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722461283\/\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/5539\/10722461283_68242773f4_z.jpg\" width=\"640\" height=\"424\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/10722293856\/\" title=\"Nine Inch Nails Live: Tension 2013\"><img src=\"https:\/\/live.staticflickr.com\/7387\/10722293856_eedc277cda_z.jpg\" width=\"427\" height=\"640\" alt=\"Nine Inch Nails Live: Tension 2013\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2738931283\/\" title=\"Nine Inch Nails Live @ Pemberton Festival - Pemberton, BC, 7.25.08\"><img src=\"https:\/\/live.staticflickr.com\/2353\/2738931283_2bcaac316c_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Pemberton Festival - Pemberton, BC, 7.25.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2739771096\/\" title=\"Nine Inch Nails Live @ Pemberton Festival - Pemberton, BC, 7.25.08\"><img src=\"https:\/\/live.staticflickr.com\/2103\/2739771096_6fda018740_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Pemberton Festival - Pemberton, BC, 7.25.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2826802685\/\" title=\"Nine Inch Nails Live @ Q Arena - Cleveland, OH, 8.22.08\"><img src=\"https:\/\/live.staticflickr.com\/3100\/2826802685_f425c65f80_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Q Arena - Cleveland, OH, 8.22.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3202016537\/\" title=\"Nine Inch Nails Live @ Coliseum - Lisbon, Portugal, 2.11.07\"><img src=\"https:\/\/live.staticflickr.com\/3079\/3202016537_6c2dd2e850_z.jpg\" width=\"480\" height=\"640\" alt=\"Nine Inch Nails Live @ Coliseum - Lisbon, Portugal, 2.11.07\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2738948949\/\" title=\"Nine Inch Nails Live @ Key Arena - Seattle, WA, 7.26.08\"><img src=\"https:\/\/live.staticflickr.com\/3223\/2738948949_a6e508137f_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ Key Arena - Seattle, WA, 7.26.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><h3 id=\"branches-coverage\">Branches Coverage <a href=\"#branches-coverage\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3032765125\/\" title=\"Nine Inch Nails Live @ DCU Center - Worcester, MA, 11.09.08 (with surprise guest Peter Murphy)\"><img src=\"https:\/\/live.staticflickr.com\/3044\/3032765125_0cea90d73b_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ DCU Center - Worcester, MA, 11.09.08 (with surprise guest Peter Murphy)\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2997970466\/\" title=\"Soundcheck in Buenos Aires, Argentina - 10.01.08\"><img src=\"https:\/\/live.staticflickr.com\/3143\/2997970466_c27cdaef49_z.jpg\" width=\"427\" height=\"640\" alt=\"Soundcheck in Buenos Aires, Argentina - 10.01.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2997970974\/\" title=\"Soundcheck in Buenos Aires, Argentina - 10.01.08\"><img src=\"https:\/\/live.staticflickr.com\/3012\/2997970974_2cb4dec9ba_z.jpg\" width=\"427\" height=\"640\" alt=\"Soundcheck in Buenos Aires, Argentina - 10.01.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2739752862\/\" title=\"Nine Inch Nails Soundcheck - Pemberton, BC, 7.24.08\"><img src=\"https:\/\/live.staticflickr.com\/2307\/2739752862_346b683bcb_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Soundcheck - Pemberton, BC, 7.24.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2738913701\/\" title=\"Nine Inch Nails Soundcheck - Pemberton, BC, 7.24.08\"><img src=\"https:\/\/live.staticflickr.com\/3201\/2738913701_9b991d6e95_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Soundcheck - Pemberton, BC, 7.24.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/2997202615\/\" title=\"Soundcheck in Mexico City, Mexico - 10.17.08\"><img src=\"https:\/\/live.staticflickr.com\/3061\/2997202615_04151a1234_z.jpg\" width=\"640\" height=\"426\" alt=\"Soundcheck in Mexico City, Mexico - 10.17.08\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3017100564\/\" title=\"Nine Inch Nails Live @ House Of Blues - Atlantic City, New Jersey, 11.06.08 (with surprise guest Peter Murphy)\"><img src=\"https:\/\/live.staticflickr.com\/3017\/3017100564_0a48c0b815_z.jpg\" width=\"640\" height=\"426\" alt=\"Nine Inch Nails Live @ House Of Blues - Atlantic City, New Jersey, 11.06.08 (with surprise guest Peter Murphy)\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script><h3 id=\"closing\">Closing <a href=\"#closing\">\ud83d\udd17<\/a><\/h3><a data-flickr-embed=\"true\" data-header=\"true\" data-footer=\"true\" href=\"https:\/\/www.flickr.com\/photos\/nineinchnails\/3639141695\/\" title=\"Nine Inch Nails live @ DTE Energy Music Theatre, Clarkston, MI, 5.31.09\"><img src=\"https:\/\/live.staticflickr.com\/3360\/3639141695_836a8f0745_z.jpg\" width=\"427\" height=\"640\" alt=\"Nine Inch Nails live @ DTE Energy Music Theatre, Clarkston, MI, 5.31.09\"><\/a><script async src=\"\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script>"},{"title":"Enough Coverage To Beat The Band at RubyConf","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2020\/","pubDate":"Wed, 16 Sep 2020 19:47:00 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2020\/","description":"<p>I&rsquo;m honored to be speaking as part of RubyConf 2020, which you can join from<a href=\"http:\/\/rubyconf.org\/\">wherever<\/a> you are. I hope you&rsquo;ll join <a href=\"http:\/\/rubyconf.org\/program\/sessions#session-1044\">my session<\/a> and many others from Nov17 - 19, 2020.<\/p><h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>The lights cut out. The crowd roars. It\u2019s time. The band takes the stage. They\u2019ve practiced the songs, particularly the covers. They\u2019ve sound checked the coverage of the speakers. They know the lighting rig has the proper colored gels covering the lamps. They\u2019re nervous, but they\u2019ve got it all covered.<\/p><p>Similarly, code coverage can give you confidence before your app performs on production and also tell you how live code is used (or not used). We\u2019ll cover how to leverage ruby\u2019s different coverage measurement techniques in concert to assist your crew and delight your audience.<\/p>"},{"title":"Fake It While You Make It","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2020\/","pubDate":"Mon, 06 Apr 2020 20:05:03 -0400","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2020\/","description":"<p>I&rsquo;m honored to be speaking as a part of RailsConf 2020, which you can join fromthe comfort of your own <a href=\"https:\/\/railsconf.com\/\">couch<\/a>! I hope you&rsquo;ll join <a href=\"https:\/\/railsconf.com\/2020\/video\/kevin-murphy-fake-it-while-you-make-it\">my session<\/a> and many otherswhen they are released on May 5, 2020.<\/p><h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>We all write code to interface with external systems, like a web service or a message queue. Can you confidently write tests without requiring the system as a dependency? How can you shield users of your code from the inner workings of the interface? Explore one attempt to answer these questions.<\/p><p>There&rsquo;s no shortage of tools at your disposal to solve these problems. This talk will introduce some available options, provide guidance on when one approach may be more appropriate than another, and discuss how to use these tools together to ease the testing process.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/iEfpAp2sqiw\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/youtu.be\/iEfpAp2sqiw\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/fake-it-while-you-make-it\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/testing-services\">Sample App<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/testing-services#testing-methods\">Code Examples<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/fake-it-while-you-make-it-proposal\/\">Proposal<\/a><\/li><li>Blog Post <a href=\"https:\/\/kevinjmurphy.com\/posts\/testing-dependencies\/\">Testing Dependencies: Fake It While You Make It<\/a><\/li><li>Blog Post <a href=\"https:\/\/kevinjmurphy.com\/posts\/designing-dependency-domain\/\">Designing A Dependency&rsquo;s Domain<\/a><\/li><\/ul>"},{"title":"Coding for Fun (Not Profit)","link":"https:\/\/kevinjmurphy.com\/posts\/coding-for-fun-not-profit\/","pubDate":"Tue, 31 Dec 2019 08:56:04 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/coding-for-fun-not-profit\/","description":"<p>I originally wanted to call this post &ldquo;Coding for Fun <del>And Profit<\/del>&rdquo; with AndProfit in strikethrough, but hugo wouldn&rsquo;t recognize markdown in the title of anew post, so I gave up immediately and renamed it, which is a perfectencapsulation of what I&rsquo;m looking to convey here.<\/p><h2 id=\"background\">Background <a href=\"#background\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;ve been paid to write code for almost 15 years now. I think I&rsquo;m decent at it,and I enjoy doing it as a profession, but it&rsquo;s not something I do much of in myfree time. I&rsquo;m not against people having side projects, spending their freetime how they&rsquo;d like and doing whatever energizes them, but I write enough codeduring the day at work to satisfy my interests there. I&rsquo;ll never get a jobsomewhere that requires an extensive personal portfolio to show as proof of&ldquo;passion&rdquo;. I&rsquo;m comfortable with that, and privileged enough to be able to saythat.<\/p><p>Before this, I&rsquo;ve also never had a personal website - ever. Sure, I maintainvarious profiles on different social networks, but I&rsquo;ve never had a place of myown on the internet. I&rsquo;m also probably the only Rails developer who&rsquo;s nevertyped <code>git push heroku<\/code> in a terminal, but that&rsquo;s a different story.<\/p><h2 id=\"coding-for-work\">Coding For Work <a href=\"#coding-for-work\">\ud83d\udd17<\/a><\/h2><p>I&rsquo;m a pretty methodical person in general, and I take that with me to work. Itcomes in handy in this business, but as with everything, it has a cost. It takestime and effort, but my belief is that the value for my employer or clientoutweighs that cost. Some of these items include:<\/p><ul><li>Requirements vetting<\/li><li>Alternatives analysis<\/li><li>Automated testing, whether through TDD or not<\/li><li>Code review<\/li><li>Maintenance and upkeep of dependencies<\/li><li>Clean commit history throughout the project<\/li><\/ul><p>Because the results of my actions affect the overall product, my team members,and all of my business partners, I&rsquo;m careful and deliberate in the work that Ido.<\/p><h2 id=\"coding-for-me\">Coding For Me <a href=\"#coding-for-me\">\ud83d\udd17<\/a><\/h2><p>I decided to stand this site up because I had some content I wanted to maintain,and I had the blessing of my employer to do so. I needed something quick andfast that I wouldn&rsquo;t need to put a lot of effort into maintaining.<\/p><p>I also wanted to try out something new, so knowing I only needed a static sitegenerator, rather than reaching for my comfortable <a href=\"https:\/\/middlemanapp.com\/\">Middleman<\/a>tool, I instead gave <a href=\"https:\/\/gohugo.io\/\">hugo<\/a> a try. After all, it is:<\/p><blockquote><p>The world&rsquo;s fastest framework for building websites<\/p><\/blockquote><p>In doing so, I of course ran into some hiccups and problems, but what I&rsquo;ll talkabout here is the process, and how this was different than how I regularly work.<\/p><h3 id=\"what-goes\">What Goes <a href=\"#what-goes\">\ud83d\udd17<\/a><\/h3><h4 id=\"alternatives--upfront-analysis\">Alternatives &amp; Upfront Analysis <a href=\"#alternatives--upfront-analysis\">\ud83d\udd17<\/a><\/h4><p>I went with hugo on name recognition only. I didn&rsquo;t even identify the corefeature set I needed or didn&rsquo;t need; I just dove right in. Similarly, I could havearduously reviewed the vast number of <a href=\"https:\/\/themes.gohugo.io\/\">themes<\/a> intheir gallery to find the perfect one, or customized my own. Instead, I chosethree that seemed &ldquo;fine&rdquo; above the fold, and picked the one that I liked bestafter a one minute review.<\/p><h4 id=\"focus\">Focus <a href=\"#focus\">\ud83d\udd17<\/a><\/h4><p>When I work from home, I do so from my office, with the door closed. I controlthe environment and am particular about what noises or stimuli are present.However, this is just for me, so instead I sat down on the couch next to my wifewhile she was watching something on TV. It&rsquo;s ok if it takes a bit longer, or I&rsquo;ma bit distracted, as long as I&rsquo;m making that trade for me and me alone.<\/p><h3 id=\"what-stays\">What Stays <a href=\"#what-stays\">\ud83d\udd17<\/a><\/h3><h4 id=\"verification\">Verification <a href=\"#verification\">\ud83d\udd17<\/a><\/h4><p>Testing brings comfort to me and I dislike working without tests. It helps guidemy initial implementation, gives safety for future refactors, and providesconfidence in delivering functionality. And most of the work that I like doingthe most, and as such gravitate towards, rarely has a direct user-facingcomponent.<\/p><p>However, that&rsquo;s <strong>all<\/strong> that this site is. I don&rsquo;t want to spend time futzingwith configurations that I don&rsquo;t need to, or meddling with the theme to eek outperformance improvements, or evaluating updating, changing, or removingdependencies. But I still need to make sure it works. I spent a ton more time inthe browser than I typically do developing for work, but it&rsquo;s the same process:<\/p><ul><li>Identify a thin slice of functionality<\/li><li>Focus on implementing it<\/li><li>Document other observations for future work<\/li><li>Iterate to the next thin slice<\/li><li>Deliver<\/li><\/ul><p>The mechanism by which I worked that process changed (refreshing the browser vs.running a test), but the process itself stayed the same.<\/p><h4 id=\"documentation\">Documentation <a href=\"#documentation\">\ud83d\udd17<\/a><\/h4><p>Sure, my commit messages are definitely not to the degree that I would expect,hope, or want them to be. However, that doesn&rsquo;t mean that they&rsquo;re worthless.Particularly on these projects where I&rsquo;m moving fast, their value is more as&ldquo;save points&rdquo; than documentation (and I&rsquo;m not cleaning them up prior to movingthem into a mainline branch), but they can still provide valuable information on<em>why<\/em> a change was made.<\/p><p>Additionally, particularly because this is a project that I&rsquo;m likely to berevisiting infrequently, I made sure to take the time to add a README thatincludes quick examples of all the things I&rsquo;m going to want to do. For thisproject, that includes:<\/p><ul><li>Creating a new post.<\/li><li>Running the server locally.<\/li><li>Deploying the changes (which there&rsquo;s a script for).<\/li><\/ul><p><em>Because<\/em> it&rsquo;s something I won&rsquo;t touch day-to-day, it&rsquo;s even <strong>more<\/strong> importantto take the few minutes to document those quick commands to save myself the timeof needing to search for it every time that I want to edit this.<\/p><h3 id=\"what-emerges\">What Emerges <a href=\"#what-emerges\">\ud83d\udd17<\/a><\/h3><h4 id=\"quick-assessment-of-good-enough\">Quick Assessment of &ldquo;Good Enough&rdquo; <a href=\"#quick-assessment-of-good-enough\">\ud83d\udd17<\/a><\/h4><p>There&rsquo;s always an ideal version of what you&rsquo;re looking to accomplish. Thatlikely manifests itself both in the code itself, as well as the user-facingproduct. And we all want to hit that ideal; however, it&rsquo;s not always feasible.And while I work diligently to reach that at work, I also do my best to bepragmatic about at least explaining the implications of getting to that ideal,presenting that to the decision-makers, and working with them to come to anagreement on how to proceed.<\/p><p>However, in this example, my goal was speed and speed alone. For example, I hada post to add which had some images that I wanted formatted just so. I couldn&rsquo;tfigure out exactly how to position or size them to my liking. I think fromsearching for about 30 minutes I could have gotten there by having the images as<a href=\"https:\/\/gohugo.io\/content-management\/page-resources\/\">page resources<\/a>, then usinghugo&rsquo;s <a href=\"https:\/\/gohugo.io\/content-management\/image-processing\/\">image processing<\/a>,and having those available in a <a href=\"https:\/\/gohugo.io\/content-management\/shortcodes\/\">shortcode<\/a>that I reference in my post.<\/p><p>I could have done that, but instead - I deleted the images. They weren&rsquo;ttremendously important to the post. They certainly made things look nicer, butthey didn&rsquo;t provide any explanatory value. So I removed them, and freed myselfof the problem. And the page doesn&rsquo;t look exactly how I wanted it to, but it&rsquo;sdone. And if I feel the need to scratch that itch in the future, I know where tostart.<\/p><p>Remember the context under which you&rsquo;re developing, who the decision-makers are,and what choices you should make based on that. But work to know what you<strong>aren&rsquo;t<\/strong> willing to give up in your process, regardless of the context.<\/p>"},{"title":"Diffin' Dots","link":"https:\/\/kevinjmurphy.com\/posts\/diffin-dots\/","pubDate":"Mon, 30 Dec 2019 21:04:53 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/diffin-dots\/","description":"<h2 id=\"a-sweet-surprise\">A Sweet Surprise <a href=\"#a-sweet-surprise\">\ud83d\udd17<\/a><\/h2><p>Recently, a project needed to re-order some commits on a git branch. Weinitially performed this interactive rebase on a separate branch, so we didn&rsquo;tmake a mistake on the original branch. After performing that rebase, we thenused Github&rsquo;s <a href=\"https:\/\/help.github.com\/en\/github\/committing-changes-to-your-project\/comparing-commits-across-time#comparing-branches\">compare view<\/a>, expecting to see no files changed.<\/p><p>Instead, what we saw was every change that was introduced in all of the commitsthat were reordered on the new branch. This very much surprised us. We made nofunctional changes and were expecting to use this comparison as proof that wedidn&rsquo;t accidentally drop a commit or do something terrible on accident.<\/p><p>We then looked to compare the branches locally. When we did that, we saw nodiff.<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 diff-test|reorder git diff master<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">(<\/span>END<span style=\"color:#f92672\">)<\/span><\/span><\/span><\/code><\/pre><\/div><p>Did we find a bug in Github? Did our re-ordering work? At this point, we had noidea.<\/p><h2 id=\"read-the-recipe\">Read The Recipe <a href=\"#read-the-recipe\">\ud83d\udd17<\/a><\/h2><p>We dug into the documentation for <code>diff<\/code>, a tool I use frequently,but don&rsquo;t spend much time considering how it works.<\/p><p>Let&rsquo;s read some of the help on how to use the diff tool:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 git diff --help<\/span><\/span><span style=\"display:flex;\"><span>GIT-DIFF<span style=\"color:#f92672\">(<\/span>1<span style=\"color:#f92672\">)<\/span> Git Manual GIT-DIFF<span style=\"color:#f92672\">(<\/span>1<span style=\"color:#f92672\">)<\/span><\/span><\/span><span style=\"display:flex;\"><span>...<\/span><\/span><span style=\"display:flex;\"><span> git diff <span style=\"color:#f92672\">[<\/span>&lt;options&gt;<span style=\"color:#f92672\">]<\/span> &lt;commit&gt; <span style=\"color:#f92672\">[<\/span>--<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">[<\/span>&lt;path&gt;...<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> This form is to view the changes you have in your working tree relative to the named &lt;commit&gt;.<\/span><\/span><span style=\"display:flex;\"><span> You can use HEAD to compare it with the latest commit, or a branch name to compare with the tip<\/span><\/span><span style=\"display:flex;\"><span> of a different branch.<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> git diff <span style=\"color:#f92672\">[<\/span>&lt;options&gt;<span style=\"color:#f92672\">]<\/span> &lt;commit&gt; &lt;commit&gt; <span style=\"color:#f92672\">[<\/span>--<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">[<\/span>&lt;path&gt;...<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> This is to view the changes between two arbitrary &lt;commit&gt;.<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> git diff <span style=\"color:#f92672\">[<\/span>&lt;options&gt;<span style=\"color:#f92672\">]<\/span> &lt;commit&gt;..&lt;commit&gt; <span style=\"color:#f92672\">[<\/span>--<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">[<\/span>&lt;path&gt;...<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> This is synonymous to the previous form. If &lt;commit&gt; on one side is omitted, it will have the<\/span><\/span><span style=\"display:flex;\"><span> same effect as using HEAD instead.<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span> git diff <span style=\"color:#f92672\">[<\/span>&lt;options&gt;<span style=\"color:#f92672\">]<\/span> &lt;commit&gt;...&lt;commit&gt; <span style=\"color:#f92672\">[<\/span>--<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">[<\/span>&lt;path&gt;...<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> This form is to view the changes on the branch containing and up to the second &lt;commit&gt;, starting<\/span><\/span><span style=\"display:flex;\"><span> at a common ancestor of both &lt;commit&gt;.<\/span><\/span><\/code><\/pre><\/div><p>Almost exclusively I&rsquo;ve used diff in the form of <code>git diff other-ref<\/code>. Andwith that, I would see what, if any, file changes occurred between those twobranches or commits. This is the two-dot comparison. However, Github uses the<a href=\"https:\/\/help.github.com\/en\/github\/collaborating-with-issues-and-pull-requests\/about-comparing-branches-in-pull-requests#three-dot-and-two-dot-git-diff-comparisons\">three-dot comparison<\/a>by default.<\/p><p>In this scenario, the common ancestor is the latest commit on the reorderingbranch that wasn&rsquo;t reordered. After the rebase, all of those reordered commitshave different SHAs, so are seen as different commits. As such, using thethree-dot option, git considers all of those SHAs as different or new, so all ofthem show as changes, even though there are no differences in the file contentsthemselves.<\/p><h2 id=\"homemade-flavor\">Homemade Flavor <a href=\"#homemade-flavor\">\ud83d\udd17<\/a><\/h2><p>Let&rsquo;s look at demonstrating this behavior entirely locally.<\/p><h3 id=\"setup\">Setup <a href=\"#setup\">\ud83d\udd17<\/a><\/h3><p>First, let&rsquo;s create a new repository:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>\u21d2 mkdir diff-test<\/span><\/span><span style=\"display:flex;\"><span>\u21d2 cd diff-test<\/span><\/span><span style=\"display:flex;\"><span>diff-test|\u21d2 git init .<\/span><\/span><span style=\"display:flex;\"><span>Initialized empty Git repository<\/span><\/span><\/code><\/pre><\/div><p>We&rsquo;ll start by adding a few new files, and making the first commit:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|master \u21d2 echo <span style=\"color:#e6db74\">&#34;Creamy Vanilla Ice Cream done Dippin\u2019 Dots style&#34;<\/span> &gt; vanilla.txt<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 echo <span style=\"color:#e6db74\">&#34;Creamy Milk Chocolate Ice Cream. Someone pass the spoon&#34;<\/span> &gt; chocolate.txt<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git add --all<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git commit -m <span style=\"color:#e6db74\">&#34;IFO: initial flavor offering&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><p>We will then add two more commits with some additional changes:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|master \u21d2 echo <span style=\"color:#e6db74\">&#34;Dippin&#39; Dots Strawberry Ice Cream is as sweet as a summer&#39;s day and so berry, berry good&#34;<\/span> &gt; strawberry.txt<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git add --all<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git commit -m <span style=\"color:#e6db74\">&#34;Introducing strawberry&#34;<\/span><\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>diff-test|master \u21d2 echo <span style=\"color:#e6db74\">&#34;Orange Flavored Ice&#34;<\/span> &gt; orange.txt<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git add --all<\/span><\/span><span style=\"display:flex;\"><span>diff-test|master\u26a1 \u21d2 git commit -m <span style=\"color:#e6db74\">&#34;Introducing orange&#34;<\/span><\/span><\/span><\/code><\/pre><\/div><h3 id=\"introduce-a-change\">Introduce a change <a href=\"#introduce-a-change\">\ud83d\udd17<\/a><\/h3><p>At this point, we realize that it&rsquo;s important to us that orange be introduced inour history before strawberry.<\/p><p>Let&rsquo;s create a new branch to address this, and look at the current order of thecommits:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|master \u21d2 git checkout -b reorder<\/span><\/span><span style=\"display:flex;\"><span>Switched to a new branch <span style=\"color:#e6db74\">&#39;reorder&#39;<\/span><\/span><\/span><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git log --oneline<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>e378256 <span style=\"color:#f92672\">(<\/span>HEAD -&gt; reorder, master<span style=\"color:#f92672\">)<\/span> Introducing orange<\/span><\/span><span style=\"display:flex;\"><span>f455e5b Introducing strawberry<\/span><\/span><span style=\"display:flex;\"><span>e42566b IFO: initial flavor offering<\/span><\/span><\/code><\/pre><\/div><p>We will then perform an interactive rebase, going back two commits in history.During that rebase, we will switch the order of orange and strawberry, so orangeis first, as we need it:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git rebase -i HEAD~2<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>pick e378256 Introducing orange<\/span><\/span><span style=\"display:flex;\"><span>pick f455e5b Introducing strawberry<\/span><\/span><\/code><\/pre><\/div><p>After performing the rebase, here is the order of our commits:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git log --oneline<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>5fa36f4 <span style=\"color:#f92672\">(<\/span>HEAD -&gt; reorder<span style=\"color:#f92672\">)<\/span> Introducing strawberry<\/span><\/span><span style=\"display:flex;\"><span>7a1d500 Introducing orange<\/span><\/span><span style=\"display:flex;\"><span>e42566b IFO: initial flavor offering<\/span><\/span><\/code><\/pre><\/div><p>Congratulations! We&rsquo;ve done what we set out to do, performing a rebase toreorder our commits. Now let&rsquo;s compare that to our original branch to make sureeverything looks to be as we&rsquo;d expect.<\/p><h3 id=\"differences\"><code>diff<\/code>erences <a href=\"#differences\">\ud83d\udd17<\/a><\/h3><p>We start by comparing this branch to the original branch:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git diff master<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">(<\/span>END<span style=\"color:#f92672\">)<\/span><\/span><\/span><\/code><\/pre><\/div><p>We see that there&rsquo;s no difference, which we&rsquo;re expecting and hoping for. Nofiles changed, no functionality is different; only the history has changed.<\/p><p>If we diff using the two-dot version, we see the same result:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git diff master..reorder<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">(<\/span>END<span style=\"color:#f92672\">)<\/span><\/span><\/span><\/code><\/pre><\/div><p>That sounds right, because according to the documentation, the two-dot usage isthe same as not providing dots:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span> git diff <span style=\"color:#f92672\">[<\/span>&lt;options&gt;<span style=\"color:#f92672\">]<\/span> &lt;commit&gt;..&lt;commit&gt; <span style=\"color:#f92672\">[<\/span>--<span style=\"color:#f92672\">]<\/span> <span style=\"color:#f92672\">[<\/span>&lt;path&gt;...<span style=\"color:#f92672\">]<\/span><\/span><\/span><span style=\"display:flex;\"><span> This is synonymous to the previous form. If &lt;commit&gt; on one side is omitted, it will have the<\/span><\/span><span style=\"display:flex;\"><span> same effect as using HEAD instead.<\/span><\/span><\/code><\/pre><\/div><p>Lastly, let&rsquo;s perform a three-dot diff:<\/p><div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>diff-test|reorder \u21d2 git diff master...reorder<\/span><\/span><span style=\"display:flex;\"><span><\/span><\/span><span style=\"display:flex;\"><span>diff --git a\/orange.txt b\/orange.txt<\/span><\/span><span style=\"display:flex;\"><span>new file mode <span style=\"color:#ae81ff\">100644<\/span><\/span><\/span><span style=\"display:flex;\"><span>index 0000000..09d8732<\/span><\/span><span style=\"display:flex;\"><span>--- \/dev\/null<\/span><\/span><span style=\"display:flex;\"><span>+++ b\/orange.txt<\/span><\/span><span style=\"display:flex;\"><span>@@ -0,0 +1 @@<\/span><\/span><span style=\"display:flex;\"><span>+Orange Flavored Ice<\/span><\/span><span style=\"display:flex;\"><span>diff --git a\/strawberry.txt b\/strawberry.txt<\/span><\/span><span style=\"display:flex;\"><span>new file mode <span style=\"color:#ae81ff\">100644<\/span><\/span><\/span><span style=\"display:flex;\"><span>index 0000000..6ab358d<\/span><\/span><span style=\"display:flex;\"><span>--- \/dev\/null<\/span><\/span><span style=\"display:flex;\"><span>+++ b\/strawberry.txt<\/span><\/span><span style=\"display:flex;\"><span>@@ -0,0 +1 @@<\/span><\/span><span style=\"display:flex;\"><span>+Dippin<span style=\"color:#e6db74\">&#39; Dots Strawberry Ice Cream is as sweet as a summer&#39;<\/span>s day and so berry, berry good<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f92672\">(<\/span>END<span style=\"color:#f92672\">)<\/span><\/span><\/span><\/code><\/pre><\/div><p>Here is where we see that it looks like each of those files have been changed asa result of this rebase. However, the files themselves <strong>haven&rsquo;t<\/strong> changed. Whathas is the commits themselves. Their common ancestor is the initial commit ofthe repository, so everything that happened since then shows as a difference.<\/p><h2 id=\"the-cherry-on-top\">The Cherry On Top <a href=\"#the-cherry-on-top\">\ud83d\udd17<\/a><\/h2><p>Locally, unless you tell git otherwise, <code>diff<\/code> will show you the file changedifferences between your comparisons. Github by default will show you thechanges from the common ancestor between what&rsquo;s changed.<\/p><p>Most of the time, this different behavior won&rsquo;t or shouldn&rsquo;t matter in thecourse of your workflow. However, if you&rsquo;re doing something a bit moreadventurous, or perhaps ill-advised, knowing how git will, by default, comparechanges locally and how Github, by default, will surface those changes can beparamount.<\/p><blockquote><p>This post originally published on <a href=\"https:\/\/blog.thegnar.co\/diffin-dots\">The Gnar Company blog<\/a>.<\/p><\/blockquote>"},{"title":"Don\u2019t Hang Me Out To DRY","link":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2019\/","pubDate":"Mon, 30 Dec 2019 20:57:49 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/rubyconf-2019\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>Close your eyes and imagine the perfect codebase to work on. I bet you\u2019ll say it has complete test coverage. It\u2019s fully-optimized, both in terms of performance and architectural design. And, of course, it contains only DRY code. Surely we can all agree that this is an aspirational situation. But&hellip;do we <strong>really<\/strong> want that?<\/p><p>Don\u2019t get me wrong; these qualities are all beneficial. However, if we also think we should value everything in moderation, when should we push back on these ideals? What problems can they introduce? Let\u2019s talk about the exceptions to some of the \u201crules\u201d we all hold dear.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/b960MApGA7A\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/www.youtube.com\/watch?v=b960MApGA7A\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/dont-hang-me-out-to-dry\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/ivory-tower\">Sample App<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/ivory-tower#code-examples\">Code Examples<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/dont-hang-me-out-to-dry-proposal\/\">Proposal<\/a><\/li><\/ul><h2 id=\"common-guiding-principles\">Common Guiding Principles <a href=\"#common-guiding-principles\">\ud83d\udd17<\/a><\/h2><p>Full test coverage, DRY code, and optimized code are all incredibly valuable, and we, as craftspeople, are better off for having them overall. But when should we push back on these ideals? What problems can they introduce?<\/p><h3 id=\"code-coverage\">Code Coverage <a href=\"#code-coverage\">\ud83d\udd17<\/a><\/h3><p>Code coverage provides a valuable signal about the extent to which some code is tested, but it is not sufficient for a quality metric. 100% test coverage does not mean all code paths are fully exercised, just that all lines are hit at least once in the execution of the test suite. An application with 100% test coverage can still have bugs and can still have sections of code that don&rsquo;t have all scenarios tested. Additionally, testing every line of code has a cost that is felt initially in the time to conceive of the tests and the test strategy. However, the larger costs are felt over time if it leads to a long test suite run time, flakey or inconsistent tests, and a large suite that needs to be continually maintained as both the technologies and requirements of the application change.<\/p><h3 id=\"dry-code\">DRY Code <a href=\"#dry-code\">\ud83d\udd17<\/a><\/h3><p>DRY code helps to ensure you don&rsquo;t have to make a change in multiple places when the way the system should work changes; instead, everything is in the isolated abstraction. However, not repeating yourself can lead to premature optimizations or abstractions that end up saddling the codebase with a difficult-to-change architecture when it\u2019s discovered that use cases aren\u2019t as similar as initially thought. Certainly reach for design patterns and well-known architectural principles when the opportunity presents itself. But, consider how one might back out of such an introduction should it turn out that the choice was premature - or hold off on introducing it entirely until it\u2019s more clear or there\u2019s sufficient churn in that area of the codebase to warrant the attention.<\/p><h3 id=\"performant-code\">Performant Code <a href=\"#performant-code\">\ud83d\udd17<\/a><\/h3><p>Performant code is objectively better than non-performant code, right? As always, the answer is, &ldquo;maybe.&rdquo; Code that is preemptively performance-tuned may or may not be necessary or accurate. Without the data and benchmarking, under load, to illustrate the performance impact, any improvements made in service of performance are done so based on conjecture. This could lead to unnecessary time in developing the believed performance benefit or may even introduce subtle bugs.<\/p>"},{"title":"I Know I Can, But Should I?","link":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2019\/","pubDate":"Mon, 30 Dec 2019 17:40:50 -0500","guid":"https:\/\/kevinjmurphy.com\/posts\/railsconf-2019\/","description":"<h2 id=\"abstract\">Abstract <a href=\"#abstract\">\ud83d\udd17<\/a><\/h2><p>You <strong>can<\/strong> use a hammer to drive a screw into wood, but I\u2019d recommend a screwdriver. Why? And when is a hammer the better option? This talk will propose a framework to use when comparing alternative technical choices. I won\u2019t decide for you, but will leave you with a structure to apply in your decision-making process.<\/p><p>The ruby toolbox is vast. While Rails provides a default experience, it leaves plenty of room for alternatives. In learning how to do something, you may uncover different ways to accomplish the same goal. Determine which tool fits best in your situation with these lessons.<\/p><h2 id=\"presentation-resources\">Presentation Resources <a href=\"#presentation-resources\">\ud83d\udd17<\/a><\/h2><iframe width=\"560\" height=\"315\" src=\"https:\/\/www.youtube.com\/embed\/2NiePLJVjNI\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen><\/iframe><ul><li><a href=\"https:\/\/www.youtube.com\/watch?v=2NiePLJVjNI&amp;feature=youtu.be\">Video<\/a><\/li><li><a href=\"https:\/\/speakerdeck.com\/kevinmurphy\/i-know-i-can-but-should-i-evaluating-alternatives\">Slides<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/evaluating-alternatives\">Sample App<\/a><\/li><li><a href=\"https:\/\/github.com\/kevin-j-m\/evaluating-alternatives#code-examples\">Code Examples<\/a><\/li><li><a href=\"https:\/\/kevinjmurphy.com\/posts\/evaluating-alternatives-proposal\/\">Proposal<\/a><\/li><\/ul><h2 id=\"evaluating-gnarly-alternatives\">Evaluating Gnarly Alternatives <a href=\"#evaluating-gnarly-alternatives\">\ud83d\udd17<\/a><\/h2><p>At <a href=\"https:\/\/www.thegnar.co\/\">The Gnar Company<\/a>, learning about our client\u2019s needs and providing solutions is one of the most satisfying parts of our work. Rails is a great and well-worn tool in our toolbox, but we work with you to understand your needs before ascribing a particular technology or framework. Our team works hard to make it easy to work with us.<\/p><h3 id=\"impact\">Impact <a href=\"#impact\">\ud83d\udd17<\/a><\/h3><p>Building a customer portal with <a href=\"https:\/\/www.thegnar.co\/work\/dispatch.html\">Dispatch<\/a> required a keen eye on the <strong>impact<\/strong> of our decisions. Working with the team we\u2019d be handing the project off to, we ensured that any conventions we introduced met their expectations. Additionally, we needed to maintain a focus on non-functional requirements to provide fast response times and real-time communication with their customers.<\/p><h3 id=\"cost\">Cost <a href=\"#cost\">\ud83d\udd17<\/a><\/h3><p><strong>Cost<\/strong> can be more than dollars and cents. Evaluating when to develop functionality yourself or utilize existing solutions is critical. When we worked to bring <a href=\"https:\/\/www.thegnar.co\/work\/valor-performance.html\">Valor Performance<\/a>\u2019s product to market, we focused on delivering a custom experience for coaching sales professionals while relying on third-party services to complement the product with scheduling, communication, and authorization functionality. As our involvement on the project wound down, we helped interview and onboard their internal team. Investing in the long-term success of our clients, even beyond our involvement, is built into our process.<\/p><h3 id=\"maintenance\">Maintenance <a href=\"#maintenance\">\ud83d\udd17<\/a><\/h3><p>We don\u2019t limit our focus to the scope of our initial engagement; we make sure to account for any <strong>maintenance<\/strong> activities that\u2019ll need consideration throughout the life of your product. After delivering a foundational product for <a href=\"https:\/\/www.thegnar.co\/work\/qeepsake.html\">Qeepsake<\/a>, we continued to provide assistance and iterate on product features and application performance as adoption took off. While initially developing the product, we made sure to leverage best-in-breed solutions in billing and messaging to quickly provide core functionality that will continue to scale as their business grows.<\/p><h3 id=\"consistency\">Consistency <a href=\"#consistency\">\ud83d\udd17<\/a><\/h3><p>While working with a large existing development team at <a href=\"https:\/\/www.thegnar.co\/work\/level-up.html\">LevelUp<\/a>, it was critical to consider <strong>consistency<\/strong>. We ensured we were delivering code that adhered to the team\u2019s existing standards to provide a seamless hand-off that they were comfortable supporting. While we introduced our approach and experience to solving similar problems in different contexts, it was all done in a way that aligned with the larger team\u2019s process.<\/p>"}]}}