<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Felipe Vogel</title>
    <description>The latest articles on DEV Community by Felipe Vogel (@fpsvogel).</description>
    <link>https://dev.to/fpsvogel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F539050%2F499f51fe-fec4-4fe7-bbc4-80e2167e71de.jpeg</url>
      <title>DEV Community: Felipe Vogel</title>
      <link>https://dev.to/fpsvogel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fpsvogel"/>
    <language>en</language>
    <item>
      <title>Alpine.js as a Stimulus alternative</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Wed, 20 Nov 2024 17:00:00 +0000</pubDate>
      <link>https://dev.to/fpsvogel/alpinejs-as-a-stimulus-alternative-k0k</link>
      <guid>https://dev.to/fpsvogel/alpinejs-as-a-stimulus-alternative-k0k</guid>
      <description>&lt;p&gt;&lt;strong&gt;Did you know you can use Alpine.js without having inline JS in the HTML, but instead putting it in separate files, similar to Stimulus? And unlike Stimulus, Alpine allows declarative templating. Yes, in your Rails/Laravel/Django/whatever server-rendered view templates!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;First, the context&lt;/li&gt;
&lt;li&gt;Why not Turbo?&lt;/li&gt;
&lt;li&gt;Why not web components?&lt;/li&gt;
&lt;li&gt;Stimulus is incomplete on its own&lt;/li&gt;
&lt;li&gt;
Alpine.js

&lt;ul&gt;
&lt;li&gt;Alpine is like Stimulus!&lt;/li&gt;
&lt;li&gt;Alpine is NOT like Stimulus!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Examples

&lt;ul&gt;
&lt;li&gt;Example 1: toggle menu&lt;/li&gt;
&lt;li&gt;Example 2: filterable list&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;But what if I need Turbo?&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;
Bonus: Alpine plugins
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;Recently I discovered &lt;a href="https://alpinejs.dev/" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt; as an alternative to &lt;a href="https://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt; for conveniently sprinkling JavaScript into server-rendered pages—and it may even be a &lt;em&gt;better&lt;/em&gt; alternative.&lt;/p&gt;

&lt;p&gt;You may know of Alpine as that little JS library where you write inline JS in the HTML—&lt;em&gt;ewww!&lt;/em&gt; That's all I knew about it too.&lt;/p&gt;

&lt;p&gt;But it turns out that you can put the JS in separate files, as in Stimulus, and you can even &lt;em&gt;prohibit&lt;/em&gt; inline JS with the &lt;a href="https://alpinejs.dev/advanced/csp" rel="noopener noreferrer"&gt;Alpine CSP build&lt;/a&gt;. But I'm getting ahead of myself…&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the context
&lt;/h2&gt;

&lt;p&gt;At my job, I work on a fairly vanilla Rails app. One of its oddities, though, is that it uses &lt;a href="https://stimulus.hotwired.dev/" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt; and not &lt;a href="https://turbo.hotwired.dev/" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt;, its sibling library in the &lt;a href="https://hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt; suite.&lt;/p&gt;

&lt;p&gt;Below I go into why we don't use Turbo, but the point here is that Stimulus is currently our only tool for creating real-time UI functionality, &lt;strong&gt;and it hurts&lt;/strong&gt;. No one at work really likes Stimulus, I think because we're expecting too much from it and using it in ways it wasn't designed for. (More on that below.)&lt;/p&gt;

&lt;p&gt;Some teams are pushing to migrate the app to Angular. I'm doubtful whether that would be worth the effort, so I'm looking into Alpine to see if it would give us some of the conveniences of a framework like Angular, but without the huge migration and added complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not Turbo?
&lt;/h2&gt;

&lt;p&gt;I mentioned that our app at work doesn't use Turbo. A Git log search (&lt;a href="https://git-scm.com/docs/git-log#Documentation/git-log.txt--Sltstringgt" rel="noopener noreferrer"&gt;&lt;code&gt;git log -S&lt;/code&gt;&lt;/a&gt;) reveals that the developers back in 2016 removed Turbo's predecessor &lt;a href="https://github.com/turbolinks/turbolinks" rel="noopener noreferrer"&gt;Turbolinks&lt;/a&gt; from the app, with commit messages like &lt;code&gt;Get your stupid Turbolinks outta my house&lt;/code&gt; 😂&lt;/p&gt;

&lt;p&gt;Then, starting around the time Turbo was released in 2020, teams were rearranged and the customer-facing part of the app stopped being seriously worked on, until my team was formed earlier this year. During that interval, there wasn't much impetus to look for something better than just Stimulus.&lt;/p&gt;

&lt;p&gt;Also, we heavily use Lit web components from our in-house design system in a way that Turbo (with its server-centric mindset) would be an awkward fit. It's easier for us to use a JS library that plays nicely with web components by operating on the client side, as Stimulus does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not web components?
&lt;/h2&gt;

&lt;p&gt;The previous paragraph begs the question, &lt;em&gt;Why not just build your features in Lit?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's because I, along with the rest of my team, enjoy keeping Rails view templates server-side. Let's say I want to add real-time behavior to a view: if I have to move an ERB template into a Lit component and translate it into JS, it feels like I've left the realm of "JS sprinkles" and now it's more like "JS chunks".&lt;/p&gt;

&lt;p&gt;If that feels like a meaningless distinction to you, and if you find Lit components to be perfectly usable in day-to-day feature work, then &lt;strong&gt;go ahead and use Lit instead of Stimulus or Alpine!&lt;/strong&gt; I honestly wish I didn't feel as much friction as I do when making Lit components, because Lit is probably the more durable option since it's closer to web standards than Alpine.&lt;/p&gt;

&lt;p&gt;Who knows, maybe in a few months I'll write a post titled &lt;em&gt;"Web components as an Alpine.js alternative"&lt;/em&gt; 😂 I've seen efforts underway to more easily server-render web components (and not just in Node), so it may someday be possible to declare a web component within a server-side template such as ERB, using regular HTML with special attributes for the dynamic bits. In fact, I was looking for such an approach to web components when I ran across a comment saying &lt;em&gt;that's precisely what Alpine does&lt;/em&gt;, minus the web components part.&lt;/p&gt;

&lt;p&gt;All that to say, my thoughts below are subject to change when web components become easier for this use case focused on non-Node SSR, or "keep your templates and sprinkle in dynamic behavior".&lt;/p&gt;

&lt;h2&gt;
  
  
  Stimulus is incomplete on its own
&lt;/h2&gt;

&lt;p&gt;My gripe with Stimulus is that &lt;strong&gt;it's imperative, not declarative.&lt;/strong&gt; In most modern JS front-end libraries (React, Angular, Lit, etc.), you have a template that is automatically re-rendered based on changes to state that's stored in JS. But with Stimulus, as with jQuery, state is expected to be stored in the DOM, and you have to manually change the DOM in response to events. It gets tedious fast.&lt;/p&gt;

&lt;p&gt;But here's the thing: Stimulus was &lt;em&gt;intentionally&lt;/em&gt; designed this way so that it would work well alongside Turbo. To quote the &lt;a href="https://stimulus.hotwired.dev/handbook/origin#how-stimulus-differs-from-mainstream-javascript-frameworks" rel="noopener noreferrer"&gt;Stimulus Handbook&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stimulus also differs on the question of state. Most frameworks have ways of maintaining state within JavaScript objects, and then render HTML based on that state. Stimulus is the exact opposite. State is stored in the HTML, so that controllers can be discarded between page changes &lt;em&gt;[such as HTML fragments morphed in via Turbo]&lt;/em&gt;, but still reinitialize as they were when the cached HTML appears again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, Stimulus discourages storing state in JS because that state disappears whenever an element with an attached Stimulus controller is replaced by Turbo.&lt;/p&gt;

&lt;p&gt;Consequently, &lt;strong&gt;Stimulus wasn't designed for building elaborate front-end features&lt;/strong&gt;. Turbo does the heavy lifting, and Stimulus handles the leftover bits: small, generic components or behaviors. See the examples in the Stimulus Handbook (&lt;a href="https://stimulus.hotwired.dev/handbook/building-something-real#implementing-a-copy-button" rel="noopener noreferrer"&gt;a copy button&lt;/a&gt; and &lt;a href="https://stimulus.hotwired.dev/handbook/managing-state#building-a-slideshow" rel="noopener noreferrer"&gt;a slideshow&lt;/a&gt;), advice in articles on Stimulus (&lt;a href="https://thoughtbot.com/blog/taking-the-most-out-of-stimulus#prefer-general-purpose-controllers" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://boringrails.com/articles/better-stimulus-controllers/#what-may-go-wrong" rel="noopener noreferrer"&gt;2&lt;/a&gt;), and &lt;a href="https://www.stimulus-components.com/" rel="noopener noreferrer"&gt;open-source Stimulus controllers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;… But we don't use Turbo at work, and for the reasons I gave earlier it would be complicated to use Turbo in our app. So I thought a better starting point would be to find a "JS sprinkles" library that's more capable on its own than Stimulus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alpine.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Alpine is like Stimulus!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://alpinejs.dev/" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt; is similar to Stimulus in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's small.&lt;/strong&gt; The bundle sizes of Stimulus and Alpine, respectively, are &lt;a href="https://bundlephobia.com/package/stimulus@3.2.2" rel="noopener noreferrer"&gt;10.9 kB&lt;/a&gt; and &lt;a href="https://bundlephobia.com/package/alpinejs@3.14.3" rel="noopener noreferrer"&gt;15.2 kB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's SSR-friendly.&lt;/strong&gt; Both Alpine and Stimulus are used in server-rendered templates via special HTML attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The JS &lt;em&gt;can&lt;/em&gt; be in separate files.&lt;/strong&gt; This isn't obvious from the Alpine docs, where almost all of the examples have JS written inline in HTML attributes. But as we’ll see in the examples below, the JS can be put in its own separate files just as in Stimulus.

&lt;ul&gt;
&lt;li&gt;Because of Alpine's use of inline JS by default, some other comparisons of Stimulus and Alpine (&lt;a href="https://buttondown.com/bhumi/archive/what-is-alpinejs-and-how-does-it-compare-to/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=W8RrkW1ooGY" rel="noopener noreferrer"&gt;2&lt;/a&gt;) claim that Alpine lacks structure, and consequently doesn't scale well in bigger projects. But this doesn't &lt;em&gt;have&lt;/em&gt; to be true!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In fact, you can use Alpine in a way very similar to Stimulus. Using the mappings below, you can write JS with Alpine that looks the same as Stimulus, apart from different syntax and naming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://alpinejs.dev/directives/ref" rel="noopener noreferrer"&gt;&lt;code&gt;x-ref&lt;/code&gt;&lt;/a&gt; is like Stimulus &lt;a href="https://stimulus.hotwired.dev/reference/targets" rel="noopener noreferrer"&gt;targets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://alpinejs.dev/directives/on" rel="noopener noreferrer"&gt;&lt;code&gt;x-on&lt;/code&gt;&lt;/a&gt; is like Stimulus &lt;a href="https://stimulus.hotwired.dev/reference/actions" rel="noopener noreferrer"&gt;actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Where Stimulus has &lt;a href="https://stimulus.hotwired.dev/reference/lifecycle-callbacks" rel="noopener noreferrer"&gt;lifecycle callbacks&lt;/a&gt;, Alpine has these:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://alpinejs.dev/globals/alpine-data#init-functions" rel="noopener noreferrer"&gt;&lt;code&gt;init()&lt;/code&gt;&lt;/a&gt; is like Stimulus &lt;code&gt;connect()&lt;/code&gt; and &lt;code&gt;[name]TargetConnected()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://alpinejs.dev/globals/alpine-data#destroy-functions" rel="noopener noreferrer"&gt;&lt;code&gt;destroy()&lt;/code&gt;&lt;/a&gt; is like Stimulus &lt;code&gt;disconnect()&lt;/code&gt; and &lt;code&gt;[name]TargetDisconnected()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A plain &lt;code&gt;constructor()&lt;/code&gt; in an Alpine data class is like Stimulus &lt;code&gt;initialize()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I've never used Stimulus &lt;a href="https://stimulus.hotwired.dev/reference/outlets" rel="noopener noreferrer"&gt;outlets&lt;/a&gt;, so I can't confidently say whether they can be emulated in Alpine. But you probably wouldn't need them.&lt;/li&gt;

&lt;li&gt;Plain data attributes can stand in for Stimulus &lt;a href="https://stimulus.hotwired.dev/reference/values" rel="noopener noreferrer"&gt;values&lt;/a&gt;. Or if data attributes become cumbersome, see my examples below for an &lt;code&gt;x-props&lt;/code&gt; custom directive in Alpine.&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://alpinejs.dev/directives/bind#binding-styles" rel="noopener noreferrer"&gt;&lt;code&gt;x-bind&lt;/code&gt;&lt;/a&gt; for Stimulus &lt;a href="https://stimulus.hotwired.dev/reference/css-classes" rel="noopener noreferrer"&gt;CSS classes&lt;/a&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;So Alpine is more or less a superset of Stimulus.*&lt;/p&gt;

&lt;p&gt;&lt;small&gt;* Note: this is &lt;em&gt;not&lt;/em&gt; true if you're using Stimulus specifically for its being a convenient wrapper around the MutationObserver API. I couldn't find a good example of what that looks like with Stimulus, but one scenario where you need MutationObserver is to make a web component react to DOM changes (&lt;a href="https://naildrivin5.com/blog/2024/10/01/custom-elements-reacting-to-changes.html" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.spicyweb.dev/videos/2023/slotted-mutations-in-web-components/" rel="noopener noreferrer"&gt;2&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Alpine is NOT like Stimulus!
&lt;/h3&gt;

&lt;p&gt;Alpine goes beyond Stimulus in that &lt;strong&gt;it's fundamentally declarative&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Stimulus is limited to granular DOM manipulation, as in &lt;em&gt;"on X event add a 'hidden' class to elements A and B; on Y event remove the 'hidden' class from elements A and B"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But Alpine allows a declarative style, as in &lt;em&gt;"show elements A and B when Z is true"&lt;/em&gt; (see &lt;a href="https://alpinejs.dev/directives/show" rel="noopener noreferrer"&gt;&lt;code&gt;x-show&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This shift from imperative to declarative style can make your JS so much more readable, maintainable, and bug-resistant.&lt;/strong&gt; As an experiment, at work I did a Stimulus-to-Alpine conversion of an "Edit Mailing Address" form built around an in-house web component for address autocomplete. The web component emits events that my JS captures and translates into error messages on the form. I was shocked at the difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Stimulus controller was &lt;strong&gt;207 lines long&lt;/strong&gt;, filled with logic that was difficult for me to follow even though I'd written it myself just a few weeks ago—and writing it had been a nightmare of continually finding yet another bug in my code.&lt;/li&gt;
&lt;li&gt;The JS class for the Alpine component totalled &lt;strong&gt;only 60 lines&lt;/strong&gt;, was easier to write, and is actually possible to understand at a glance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;I can't actually show that example from work, so rather than making up my own new examples here, I'll just link to the two examples in Brian Schiller's post &lt;a href="https://brianschiller.com/blog/2021/11/05/alpine-stimulus-js/" rel="noopener noreferrer"&gt;"Alpine.js vs Stimulus"&lt;/a&gt;, and I'll add my own alternate Alpine versions that keep the JS out of the HTML.&lt;/p&gt;

&lt;p&gt;In my versions of the examples, I used &lt;a href="https://alpinejs.dev/advanced/csp" rel="noopener noreferrer"&gt;the CSP build of Alpine&lt;/a&gt;, where inline JS is actually impossible. This nicely serves as a form of no-inline-JS linting. It also introduces some inconveniences, but they can be worked around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passing &lt;a href="https://alpinejs.dev/globals/alpine-data#initial-parameters" rel="noopener noreferrer"&gt;initial parameters&lt;/a&gt; into a data object is impossible. No big deal: you can just use data attributes or (if that becomes clunky) a custom &lt;code&gt;x-props&lt;/code&gt; directive, which you can see in my version of the second example below.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://alpinejs.dev/directives/model" rel="noopener noreferrer"&gt;&lt;code&gt;x-model&lt;/code&gt;&lt;/a&gt; doesn't work in the CSP build. The workaround is to use the two directives that &lt;code&gt;x-model&lt;/code&gt; is a shortcut for:&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- instead of this: --&amp;gt;
&amp;lt;input x-model:"myProperty"&amp;gt;

&amp;lt;!-- do this: --&amp;gt;
&amp;lt;input :value="myProperty" @input="setMyProperty"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:value&lt;/code&gt; is short for &lt;code&gt;x-bind:value&lt;/code&gt; (&lt;a href="https://alpinejs.dev/directives/bind" rel="noopener noreferrer"&gt;docs&lt;/a&gt;), and &lt;code&gt;@input&lt;/code&gt; is short for &lt;code&gt;x-on:input&lt;/code&gt; (&lt;a href="https://alpinejs.dev/directives/on" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). This workaround makes an appearance in my version of the second example below.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example 1: toggle menu
&lt;/h3&gt;

&lt;p&gt;This is Brian Schiller's second example, but I'm putting it first because it's the simpler of the two.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brian's Stimulus version: &lt;a href="https://codepen.io/bgschiller/pen/jOLLNpj" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;, &lt;a href="https://brianschiller.com/blog/2021/11/05/alpine-stimulus-js/#stimulus-1" rel="noopener noreferrer"&gt;Brian's notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Brian's Alpine version: &lt;a href="https://codepen.io/bgschiller/pen/WNEzvLj" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;, &lt;a href="https://brianschiller.com/blog/2021/11/05/alpine-stimulus-js/#alpinejs-1" rel="noopener noreferrer"&gt;Brian's notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My Alpine CSP version: &lt;a href="https://codepen.io/fpsvogel/pen/dyxdVoz" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My notes:&lt;/strong&gt; The HTML is cleanest with Alpine CSP, and the JS file is still super short.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: filterable list
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Brian's Stimulus version: &lt;a href="https://codepen.io/bgschiller/pen/YzxQoPj" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;, &lt;a href="https://brianschiller.com/blog/2021/11/05/alpine-stimulus-js/#stimulus" rel="noopener noreferrer"&gt;Brian's notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Brian's Alpine version: &lt;a href="https://codepen.io/bgschiller/pen/LYjdEzj" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;, &lt;a href="https://brianschiller.com/blog/2021/11/05/alpine-stimulus-js/#alpinejs" rel="noopener noreferrer"&gt;Brian's notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My Alpine CSP version: &lt;a href="https://codepen.io/fpsvogel/pen/xxvYEdb" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once again the HTML is cleanest with Alpine CSP, but the JS file is not so satisfyingly short this time. Partly that's due to how this example is more complex, but another reason is that I threw in the &lt;code&gt;x-props&lt;/code&gt; custom Alpine directive, not because it was needed but just for the sake of example.&lt;/li&gt;
&lt;li&gt;I didn't love that I had to pull in an extra package to access a parent component's data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  But what if I need Turbo?
&lt;/h2&gt;

&lt;p&gt;Earlier I pointed out that the apparent shortcomings of Stimulus are by design, in order for it to be compatible with &lt;a href="https://turbo.hotwired.dev/" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt;. Does that mean you can't use Alpine with Turbo?&lt;/p&gt;

&lt;p&gt;Yes and no. If you've already built your app using Stimulus and Turbo, it might not make sense to switch from Stimulus to Alpine. Turbo is probably handling most of the real-time interactivity anyway.&lt;/p&gt;

&lt;p&gt;But if all you want is &lt;em&gt;something like Turbo&lt;/em&gt;, i.e. a way to take HTML fragments sent from the server and morph them into the page, here are Alpine-friendly options for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;htmx&lt;/a&gt;, which has an &lt;a href="https://v1.htmx.org/extensions/alpine-morph/" rel="noopener noreferrer"&gt;alpine-morph&lt;/a&gt; extension.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://alpine-ajax.js.org/" rel="noopener noreferrer"&gt;Alpine AJAX&lt;/a&gt;, which has &lt;a href="https://alpine-ajax.js.org/reference/#morphing" rel="noopener noreferrer"&gt;a similar morph feature&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd probably pick Alpine AJAX, but more importantly &lt;em&gt;I wouldn't reach for morphing without considering other approaches first&lt;/em&gt;, because morphing is complex and has limitations and edge cases. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://chrisdone.com/posts/htmx-critique/#most-interesting-web-apps-cannot-replace-wholesale-a-dom-element" rel="noopener noreferrer"&gt;a critique of htmx&lt;/a&gt; and &lt;a href="https://news.ycombinator.com/item?id=41782094" rel="noopener noreferrer"&gt;the htmx author's reply&lt;/a&gt; that it's a work in progress&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thomascannon.me/guides/fixing-the-rails-networking-stack#turbo-adds-excessive-complexity-and-reimplements-the-browser" rel="noopener noreferrer"&gt;a similar critique of Turbo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I'll piggyback on Brian's post one more time and repeat its conclusion here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can enthusiastically choose Alpine over Stimulus. Stimulus seems to have skipped the lessons of the past 7-8 years. It’s better than jQuery, but ignores the things that are good about React, Vue, etc: reactivity and declarative rendering. Meanwhile, Alpine manages to pack a ton of useful functionality into a smaller bundle size than Stimulus.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alpine's bundle size has now grown somewhat larger than that of Stimulus, but otherwise I wholeheartedly agree with that conclusion.&lt;/p&gt;

&lt;p&gt;And I'll add this: for anyone who is concerned about the maintainability of inline JS in HTML and prefers clean markup, a clear separation of concerns, TypeScript, etc. … &lt;strong&gt;you can do all that with Alpine!&lt;/strong&gt; I think Alpine is the best of both worlds, between "all-in" JS frameworks that handle rendering (React et al.) and vanilla JS: declarative and template-friendly like React et al., but at the same time, small and easy to sprinkle into your server-rendered templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Alpine plugins
&lt;/h2&gt;

&lt;p&gt;Another nice thing about Alpine is how many plugins there are out there. I already mentioned &lt;a href="https://alpine-ajax.js.org/" rel="noopener noreferrer"&gt;Alpine AJAX&lt;/a&gt;; here are just a few others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "plugins" nav section in &lt;a href="https://alpinejs.dev/start-here" rel="noopener noreferrer"&gt;the Alpine docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/markmead/alpinejs-form-validation" rel="noopener noreferrer"&gt;Form validation&lt;/a&gt; and &lt;a href="https://github.com/markmead?tab=repositories&amp;amp;q=alpine" rel="noopener noreferrer"&gt;others by Mark Mead&lt;/a&gt;, especially the &lt;a href="https://js.hyperui.dev/" rel="noopener noreferrer"&gt;HyperJS&lt;/a&gt; collection&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0wain/alpinejs-requests" rel="noopener noreferrer"&gt;Requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ryangjchandler/alpine-clipboard" rel="noopener noreferrer"&gt;Clipboard&lt;/a&gt; and &lt;a href="https://github.com/ryangjchandler?tab=repositories&amp;amp;q=alpine" rel="noopener noreferrer"&gt;others by Ryan Chandler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marcreichel/alpine-autosize" rel="noopener noreferrer"&gt;Autosize&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marcreichel/alpine-auto-animate" rel="noopener noreferrer"&gt;Auto animate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mvolkmann/alpine-plugins" rel="noopener noreferrer"&gt;&lt;code&gt;x-include&lt;/code&gt; and &lt;code&gt;x-interpolate&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find lots more in lists like &lt;a href="https://www.alpinetoolbox.com/extensions" rel="noopener noreferrer"&gt;Alpine Extensions&lt;/a&gt; and &lt;a href="https://github.com/alpine-collective/awesome#extensions--plugins" rel="noopener noreferrer"&gt;Awesome Alpine Plugins&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>rails</category>
    </item>
    <item>
      <title>Being laid off in 2023-2024 as an early-career developer</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Wed, 27 Mar 2024 14:00:00 +0000</pubDate>
      <link>https://dev.to/fpsvogel/being-laid-off-in-2023-2024-as-an-early-career-developer-3mb5</link>
      <guid>https://dev.to/fpsvogel/being-laid-off-in-2023-2024-as-an-early-career-developer-3mb5</guid>
      <description>&lt;p&gt;&lt;strong&gt;The saga of my second job search as a second-career software developer, amid waves of layoffs and a tough job market. Included: tips on getting interviews via job networking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Backstory: from teacher to developer&lt;/li&gt;
&lt;li&gt;No one is safe from being laid off, and I'm not an exception&lt;/li&gt;
&lt;li&gt;The 6-month job search&lt;/li&gt;
&lt;li&gt;My job networking by the numbers&lt;/li&gt;
&lt;li&gt;The good news&lt;/li&gt;
&lt;li&gt;
What's next in my career?
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;Recently I wrote &lt;a href="https://fpsvogel.com/posts/2024/job-search-networking-for-engineers"&gt;an upbeat how-to on job networking&lt;/a&gt;. Now comes the part where I pull back the curtain and tell how the job search &lt;em&gt;really&lt;/em&gt; went.&lt;/p&gt;

&lt;p&gt;Don't worry, it's not &lt;em&gt;all&lt;/em&gt; depressing. I've waited for weeks to publish this post just so that I have some good news to share at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post's title specifies "early-career developer" only because I'm speaking from my own experience, not because &lt;em&gt;only&lt;/em&gt; early-career developers are having a hard time. It's a tough job market for everyone in tech, even job seekers with decades of experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory: from teacher to developer
&lt;/h2&gt;

&lt;p&gt;For context, I used to be a schoolteacher. In 2019, after a sudden move back to the U.S. (Kentucky) from an overseas teaching position, I found myself earning a pitifully low salary of &lt;span&gt;$32k/year&lt;/span&gt; and inundated with medical bills from my wife's chronic illness.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I didn't even enjoy my new job.&lt;/em&gt; The frequent late nights spent planning and grading, the emotional drain of managing classrooms full of teenagers all day, the nagging feeling that in spite of all the effort I wasn't actually helping these kids… it was too much.&lt;/p&gt;

&lt;p&gt;It was time for a career change.&lt;/p&gt;

&lt;p&gt;So I quit teaching at the end of the school year, got a customer support job (remote) in the COVID-fueled e-commerce industry, and taught myself coding in the evenings and on weekends. After a year and a half, in February 2022, I got a junior developer job (also remote), and the following year I was promoted to mid-level.&lt;/p&gt;

&lt;p&gt;Meanwhile, my salary went up and up. &lt;a href="https://fpsvogel.com/posts/2024/early-career-developer-job-search-after-layoffs#salary-2020-2024"&gt;In this same post on my site&lt;/a&gt; I've included a chart showing my gross pay since the beginning of 2020, when I was still a teacher.&lt;/p&gt;

&lt;p&gt;Did you notice the gap near the end? That's the story of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  No one is safe from being laid off, and I'm not an exception
&lt;/h2&gt;

&lt;p&gt;In early 2023, my company had a 19% RIF (reduction in force, i.e. layoffs). Of my cohort of four junior developers that had joined the previous year, I was the only one who wasn't laid off.&lt;/p&gt;

&lt;p&gt;I was shaken, but also bolstered in my belief that &lt;em&gt;I'm special&lt;/em&gt;. I felt I was a great developer, and that would keep me safe from being laid off.&lt;/p&gt;

&lt;p&gt;Fast forward to November, and… &lt;strong&gt;I was laid off&lt;/strong&gt;. The immensity of this second round (44% RIF) was some consolation in that I didn't feel singled out. But at the same time, I felt uneasy seeing how all of my former junior colleagues were &lt;em&gt;still&lt;/em&gt; looking for a new role after so many months. Plus, I'd actually started applying for jobs two months earlier because I saw the layoffs coming, and I'd gotten &lt;em&gt;no responses&lt;/em&gt; from dozens of applications.&lt;/p&gt;

&lt;p&gt;Still, I clung to the belief that &lt;em&gt;I'm special&lt;/em&gt; and I'd have an easy time finding another job now that I could make my job search public and fully focus on it. After all, my previous job search in 2021 had been pretty easy, and that was when I had zero professional experience as a developer. How hard could it be this time?&lt;/p&gt;

&lt;p&gt;I was in for a big disappointment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6-month job search
&lt;/h2&gt;

&lt;p&gt;It was rough. Here are some low points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🦗 &lt;strong&gt;The crickets chirping.&lt;/strong&gt; For the first &lt;em&gt;three months&lt;/em&gt;, I applied to dozens of jobs and got &lt;strong&gt;zero&lt;/strong&gt; responses. That's a whole month after my job search went public. Then I started job networking, following a process &lt;a href="https://fpsvogel.com/posts/2024/job-search-networking-for-engineers"&gt;that I posted about previously&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;😐 &lt;strong&gt;Insane levels of competition.&lt;/strong&gt; I experienced a new level of job market saturation when I was rejected near the end of an interview process because a candidate with 16 years of experience was chosen instead… &lt;em&gt;for a mid-level role&lt;/em&gt; at a company that also had open senior positions.&lt;/li&gt;
&lt;li&gt;🤦 &lt;strong&gt;Nonsensical skill requirements.&lt;/strong&gt; One final interview was a surprise live coding exercise covering React. But here's the thing: &lt;em&gt;this was for a back-end role!&lt;/em&gt; That caught me off guard, and I didn't do well in the exercise.&lt;/li&gt;
&lt;li&gt;😵‍💫 &lt;strong&gt;So many hours spent on take-home assignments.&lt;/strong&gt; We all know that the "2-3 hours" in a take-home assignment description means about five times that much. Once, I spent 20 hours on an exercise, between the take-home portion and live coding prep. (If you're wondering &lt;em&gt;"Huh? Live coding prep?"&lt;/em&gt; it's because, unusually, they sent me the live coding exercise ahead of the interview. But it was also an unusually complex live coding exercise.)

&lt;ul&gt;
&lt;li&gt;Not to digress too much, but &lt;strong&gt;why are take-home assignments so long?&lt;/strong&gt; Why doesn't a bit of (no-prep!) live coding suffice? For example, I remember an interview process in my 2021 job search that didn't have any take-home assignment, only a live coding interview. It was a simple exercise building a CLI hangman game. The requirements were minimal and I didn't even have to finish it—the interviewer just wanted to see my thinking process, and it was very conversational. After about 15 minutes he stopped me, reiterated a few things I'd done that he liked, and said he'd be in touch. &lt;em&gt;And that was it.&lt;/em&gt; I really appreciated that the interviewer &lt;em&gt;knew what he was looking for&lt;/em&gt; and &lt;em&gt;knew how to spot it quickly&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;💀 &lt;strong&gt;Lowering my bar down, down… nope, lower.&lt;/strong&gt; In January, about five months after I'd first started searching, I accepted a job that paid significantly less than my previous salary. Not only that, but the interview process had given me all kinds of red flags, including very mixed Glassdoor reviews and the hiring manager saying that promotions and raises were on hold indefinitely. I took the job anyway because I had no idea how many more months it would take for me to get an offer from a different company, and I wanted to prevent the worst-case scenario of my savings running out. Meanwhile, I kept looking on the down-low—I had to immediately go silent on LinkedIn because my new employer made it clear to me that they didn't want me posting there anymore 🙄 Fortunately, I soon found a better job. (More on that below.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My job networking by the numbers
&lt;/h2&gt;

&lt;p&gt;Backing up a little bit, &lt;strong&gt;job networking&lt;/strong&gt; is what turned the tide and helped me start getting interviews. I described my process &lt;a href="https://fpsvogel.com/posts/2024/job-search-networking-for-engineers"&gt;in a previous post&lt;/a&gt;, and now I can share more on the results.&lt;/p&gt;

&lt;p&gt;I applied for 18 roles that I networked for, i.e. where I didn't submit a cold application. These were mostly in December and January, months 4 and 5 in the 6-month search. The networking that I did can be divided into the three broad types below (which you can see represented visually further down, if you want to skip the verbiage).&lt;/p&gt;

&lt;p&gt;Note that I applied only for remote positions, because there aren't a lot of in-person developer jobs in my area (central Kentucky), and in-person salaries here tend to be lower anyway. For anyone living in a tech hub, it's a good idea to do in-person networking too, such as attending local meetups.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Existing connections:&lt;/strong&gt; I already had a connection in the company, first- or second-degree.

&lt;ul&gt;
&lt;li&gt;Of 10 attempts, I got 6 recruiter screenings, and in 3 of those I got more interviews.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn messaging:&lt;/strong&gt; I tried forming a new connection by messaging a stranger via InMail on LinkedIn.

&lt;ul&gt;
&lt;li&gt;Of 7 attempts, only one led to a recruiter screening and then further interviews. But I only twice got no response; people were generally willing to talk to a random stranger like me, and twice I got a referral. In case you didn't know, most companies offer a referral bonus to employees after they refer a candidate who ends up being hired. So even if it feels awkward to message a complete stranger, it's worth a try because they're incentivized to refer you.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party recruiter:&lt;/strong&gt; I applied for one job through Brian at &lt;a href="https://www.mirrorplacement.com/"&gt;Mirror Placement&lt;/a&gt;, and it led to a recruiter screening and a further interview. Besides that one role, Brian said he didn't have anything for an early-career developer like me. But I enjoyed working with Brian, so I'll definitely talk to him again in the future when I have more years of experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In total, of these 18 attempts, 8 got me a recruiter screening, and in 5 of those I went on to further interviews. Two of these led to job offers: the one I accepted early out of caution, and the one for my current role. Here's all of the above visualized in a diagram built with &lt;a href="https://sankeymatic.com"&gt;SankeyMATIC&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh64hf5ya6rucy07ti5wr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh64hf5ya6rucy07ti5wr.png" alt="My 2023-2024 job search visualized in a Sankey diagram" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I should also mention that cold applications yielded a grand total of &lt;strong&gt;&lt;em&gt;one&lt;/em&gt;&lt;/strong&gt; recruiter screening, after which I was ghosted by the recruiter. I cold-applied to &lt;strong&gt;over a hundred jobs&lt;/strong&gt;, so that's a pretty terrible success rate. Granted, most of those applications were low-effort and done only to satisfy my state's unemployment requirements, but there was a sizable minority into which I put genuine effort, including painstaking answers to &lt;em&gt;"Why are you applying?"&lt;/em&gt;-type questions, and even a few cover letters.&lt;/p&gt;

&lt;p&gt;Just for fun, here is the above diagram with cold applications added in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0voqpttt0x3x38ch9iwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0voqpttt0x3x38ch9iwr.png" alt="My 2023-2024 job search visualized in a Sankey diagram, this time including a huge segment representing cold applications" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, this contrasts starkly with my last job search in 2021. It's funny to look back at my blog post about it, where &lt;a href="https://fpsvogel.com/posts/2022/how-to-find-ruby-rails-job#my-job-search-a-birds-eye-view"&gt;I wrote&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over two months, I applied to seven companies, most of them startups. I got a first-step interview or recruiter screening at six of those companies, and in five of them I moved forward to next steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The crazy part is that those were all cold applications 😳&lt;/p&gt;

&lt;p&gt;Unless I was an incredible candidate back then in a way that I now fail to recall (or recreate), clearly the job market has changed &lt;em&gt;a lot&lt;/em&gt; since two years ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good news
&lt;/h2&gt;

&lt;p&gt;Enough with the doom and gloom. I'm happy to announce that I got a job at &lt;a href="https://www.kin.com/"&gt;Kin Insurance&lt;/a&gt; as a full-stack Rails developer.&lt;/p&gt;

&lt;p&gt;Now I can finally stop job hunting, and focus on progressing my career in more productive ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next in my career?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Getting into the community.&lt;/strong&gt; Recently I was accepted as a &lt;a href="https://rubycentral.org/scholars_guides_program/"&gt;Ruby Central Scholar&lt;/a&gt;, meaning I'll give a lightning talk at RailsConf 🎉 I'm also co-organizing a local Ruby meetup, &lt;a href="https://bluegrassruby.club/"&gt;Bluegrass Ruby&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less overtime, more family time.&lt;/strong&gt; At my last job, I often worked long days in order to get a lot done and impress my peers. You can see that reflected in &lt;a href="https://www.linkedin.com/in/fpsvogel/details/recommendations/"&gt;my glowing LinkedIn recommendations&lt;/a&gt; from former colleagues—which, sadly, I'm not sure even made a difference in my job search. In any case, I'll be sticking more to a 40-hour work week because the past few months have made me re-examine my priorities, especially in light of the fact that I have a 9-month-old kid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More "me time" too.&lt;/strong&gt; I've also missed resting and investing in my own things, whether spiritual practices or reading Latin and Greek. Honestly, I'm going to have a harder time with this than anything else on this list, but I know it's important for my mental health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More consistent learning.&lt;/strong&gt; The job search also gave me a chance to get back to &lt;a href="https://github.com/fpsvogel/learn-ruby"&gt;my Ruby/web development learning roadmap&lt;/a&gt;. I realized that at my last job, I wasn't consistently spending time improving my skills, outside of whatever I might (if I was lucky) be learning in work projects. It's just &lt;strong&gt;hard&lt;/strong&gt; to fight against the pressure of the day-to-day work. Here are some approaches that I'll try this time around:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disregard immediate applicability&lt;/strong&gt; and learn something I'm interested in for the sake of expanding my mind. Right now that's &lt;a href="https://github.com/fpsvogel/learn-functional-programming"&gt;learning functional programming&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learn actively&lt;/strong&gt;, whether by &lt;a href="https://github.com/exercism/ruby/pulls?q=is%3Apr+author%3Afpsvogel"&gt;contributing to Exercism's Ruby track&lt;/a&gt;, building &lt;a href="https://github.com/fpsvogel/ruby-katas"&gt;a collection of Ruby code katas&lt;/a&gt;, or maybe even creating &lt;a href="https://github.com/fpsvogel/worlds-terminal"&gt;a text-based game&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>rails</category>
    </item>
    <item>
      <title>Coming to grips with JS: a Rubyist's deep dive</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Fri, 29 Dec 2023 15:45:43 +0000</pubDate>
      <link>https://dev.to/fpsvogel/coming-to-grips-with-js-a-rubyists-deep-dive-2oaj</link>
      <guid>https://dev.to/fpsvogel/coming-to-grips-with-js-a-rubyists-deep-dive-2oaj</guid>
      <description>&lt;p&gt;tl;dr I'm systematically learning JavaScript using these resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Because JS is inescapable in web development.&lt;/p&gt;

&lt;p&gt;Sure, you can use any number of JS-avoidance libraries. I'm a fan of &lt;a href="https://turbo.hotwired.dev/"&gt;Turbo&lt;/a&gt;, and there's also &lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt;, &lt;a href="https://unpoly.com/"&gt;Unpoly&lt;/a&gt;, &lt;a href="https://alpinejs.dev/"&gt;Alpine&lt;/a&gt;, &lt;a href="https://hyperscript.org/"&gt;hyperscript&lt;/a&gt;, &lt;a href="https://swup.js.org/"&gt;swup&lt;/a&gt;, &lt;a href="https://barba.js.org/"&gt;barba.js&lt;/a&gt;, and probably others.&lt;/p&gt;

&lt;p&gt;Then there are stack-specific libraries: &lt;a href="https://docs.stimulusreflex.com/"&gt;StimulusReflex&lt;/a&gt; for Rails, &lt;a href="https://github.com/phoenixframework/phoenix_live_view"&gt;Phoenix LiveView&lt;/a&gt;, &lt;a href="https://laravel-livewire.com/"&gt;Laravel Livewire&lt;/a&gt;, &lt;a href="https://www.django-unicorn.com/"&gt;Unicorn&lt;/a&gt; and &lt;a href="https://www.tetraframework.com/"&gt;Tetra&lt;/a&gt; for Django, &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor"&gt;Blazor&lt;/a&gt; for .NET, … and the list goes on.&lt;/p&gt;

&lt;p&gt;You get the picture. Lots of people would rather not build a JS front end.&lt;/p&gt;

&lt;p&gt;I myself avoided the JS ecosystem a few years ago when it would have been the default path for me as a beginning second-career developer. But I was going the self-taught route, so &lt;strong&gt;I needed an ecosystem with strong conventions&lt;/strong&gt;. I didn't know how to choose from a dozen popular JS frameworks. And none of them is an all-in-one, "batteries-included" framework, so it looked like I'd need to make many decisions about how to put together an app, which would mean (for me at that time, as a beginner who lacked context) &lt;strong&gt;lots of frustration and stabbing in the dark&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was in the Ruby world that I found the conventions I needed. Plus, I found (and still find) Ruby to be more enjoyable.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;now I've had enough web development experience&lt;/strong&gt; that I can circle back and learn JS thoroughly, confidently, and without wasting as much time on rabbit trails.&lt;/p&gt;

&lt;p&gt;Not that I can't get around in JS. At my last job, I was comfortable building full-stack features in Rails and React.&lt;/p&gt;

&lt;p&gt;Oh, and speaking of &lt;em&gt;my last job&lt;/em&gt;—recently I was laid off as part of a massive reduction in force. (Stay tuned for a future post on my job search and what I'm learning from it.)&lt;/p&gt;

&lt;p&gt;Being unemployed and &lt;strong&gt;seeing so many jobs involving a JS front end&lt;/strong&gt;—that's ultimately what gave me the push I needed to get serious about JS.&lt;/p&gt;

&lt;p&gt;That's what I mean when I say JS is "inescapable": not that we can't build anything without it—in fact, I quite enjoy making sites with minimal JS and plenty of interactivity. I only mean that &lt;strong&gt;JS skills are mandatory for someone like me&lt;/strong&gt; who has only a few years of experience, and therefore fewer job options. Even if I &lt;em&gt;could&lt;/em&gt; find a backend-only position, I'm not sure I want to pigeonhole myself like that.&lt;/p&gt;

&lt;p&gt;Plus, I really do enjoy full-stack development. And even if in some utopian universe I were able to land a full-stack position using some of the above-mentioned libraries instead of a heavy JS front end, it would still be important to understand what's going on behind the scenes. After all, those libraries are JS that's running on the page allowing my non-JS code to do cool interactive things.&lt;/p&gt;

&lt;p&gt;So many reasons to learn JS!&lt;/p&gt;

&lt;h2&gt;
  
  
  How?
&lt;/h2&gt;

&lt;p&gt;I'm using the resources listed below. Almost all are free. Besides a comprehensive look at JS syntax, I made sure to include a few other areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guided practice&lt;/strong&gt; and projects, to turn &lt;em&gt;knowledge&lt;/em&gt; into &lt;em&gt;skills&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web APIs&lt;/strong&gt;, especially the DOM, forms, and web components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dives&lt;/strong&gt; into how JS works, and the rationale (or at least reasons) behind its quirks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional JS&lt;/strong&gt;, because I'm interested in functional programming. I recently &lt;a href="https://fpsvogel.com/posts/2023/rubyist-learns-haskell-getting-started"&gt;started learning Haskell&lt;/a&gt;, but JS will be useful as an example of how to apply functional concepts in a not-really-functional language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's &lt;em&gt;a lot&lt;/em&gt; in the bottom two-thirds of the list, only because I haven't gone through it yet and weeded out the less-than-awesome resources.&lt;/p&gt;

&lt;p&gt;Also, note that this list is copied straight from &lt;a href="https://github.com/fpsvogel/learn-ruby#js"&gt;the "JS" section of my learning road map&lt;/a&gt;, and the latest version may have evolved from what you see here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basics:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://exploringjs.com/impatient-js/"&gt;JavaScript for impatient programmers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://peterxjang.com/blog/modern-javascript-explained-for-dinosaurs.html"&gt;Modern JavaScript Explained For Dinosaurs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtube.com/watch?v=8aGhZQkoFbQ"&gt;What the heck is the event loop anyway?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript"&gt;MDN - JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lodash.com"&gt;lodash&lt;/a&gt; and &lt;a href="https://youmightnotneed.com/lodash/"&gt;You Might Not Need Lodash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://exercism.org/tracks/javascript"&gt;Exercism - JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javascript30.com/"&gt;JavaScript30&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOM and forms:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://javascript.info/ui"&gt;The Modern JavaScript Tutorial - Browser: Document, Events, Interfaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Forms"&gt;MDN - Web forms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/learn/forms"&gt;web.dev - Learn Forms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Going deeper:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/getify/You-Dont-Know-JS"&gt;You Don't Know JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://exploringjs.com/deep-js/toc.html"&gt;Deep JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.udemy.com/course/understand-javascript/"&gt;JavaScript: Understanding the Weird Parts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justjavascript.com/"&gt;Just JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional JS:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/getify/Functional-Light-JS"&gt;Functional-Light JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mostly-adequate.gitbook.io/mostly-adequate-guide/"&gt;Professor Frisby's Mostly Adequate Guide to Functional Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/javascriptallongesix/read"&gt;JavaScript Allongé&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/playlist?list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84"&gt;Functional programming in JavaScript&lt;/a&gt; videos&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.packtpub.com/product/mastering-javascript-functional-programming/9781839213069"&gt;Mastering JavaScript Functional Programming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web components:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eisenbergeffect.medium.com/hello-web-components-795ed1bd108e"&gt;Rob Eisenberg - "Hello Web Components"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://daverupert.com/2023/01/html-with-superpowers-the-guidebook/"&gt;Dave Rupert - HTML with Superpowers: The Guidebook&lt;/a&gt; or &lt;a href="https://frontendmasters.com/courses/web-components/"&gt;the course version&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components"&gt;MDN - Web Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javascript.info/web-components"&gt;The Modern JavaScript Tutorial - Web Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webcomponents.today/"&gt;Web Components Today&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Build a UI following &lt;a href="https://www.fullstackruby.dev/fullstack-development/2022/01/04/how-ruby-web-components-work-together/"&gt;Jared White - How Ruby and Web Components Can Work Together&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SSR web components in Ruby with the upcoming &lt;a href="https://heartml-docs.onrender.com/"&gt;Heartml&lt;/a&gt; (see &lt;a href="https://www.spicyweb.dev/web-components-ssr-node/"&gt;this Spicy Web article&lt;/a&gt; for context)&lt;/li&gt;
&lt;li&gt;Experiment using &lt;a href="https://turbo.hotwired.dev/"&gt;Turbo&lt;/a&gt; to drive front-end behavior: &lt;em&gt;"Turbo 7.2.0 (currently in beta) allows you to define your own Stream actions which can be any JS code you want. By combining a custom Stream action or two with web components, you can essentially drive reactive frontend behavior from the backend stupidly easily. Loooove it! 😍 […] For a turnkey example, you could check out &lt;a href="https://github.com/hopsoft/turbo_ready"&gt;https://github.com/hopsoft/turbo_ready&lt;/a&gt; "&lt;/em&gt; —Jared White on &lt;a href="https://discord.com/channels/811491992285741077/811493083068760104/1019024338042761297"&gt;The Spicy Web&lt;/a&gt; Discord&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A word on JS frameworks
&lt;/h2&gt;

&lt;p&gt;You may be wondering why my learning plan doesn't include any JS frameworks. No React deep dives? Not even the more hip Vue or Svelte??&lt;/p&gt;

&lt;p&gt;I do plan on familiarizing myself with popular front-end frameworks, including the parts of React that I haven't used. Learning the patterns that are common across frameworks will be valuable, I think.&lt;/p&gt;

&lt;p&gt;But if there's anything I focus on, I want it to be JS itself (along with other web standards) because they're a more durable investment, changing more slowly than JS frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning JS, re-learning Ruby
&lt;/h2&gt;

&lt;p&gt;Readers who aren't into Ruby can feel free to leave now (it's OK, I won't feel bad), but I wanted to conclude by showing how &lt;strong&gt;learning JS has helped me re-learn Ruby features that I rarely use&lt;/strong&gt;. Here are two examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object destructuring
&lt;/h3&gt;

&lt;p&gt;In JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Willard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wilbur&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wonka&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you know Ruby can do something similar with hash destructuring?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;obj_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;first: &lt;/span&gt;&lt;span class="s2"&gt;"Willard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;middle: &lt;/span&gt;&lt;span class="s2"&gt;"Wilbur"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last: &lt;/span&gt;&lt;span class="s2"&gt;"Wonka"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# `=&amp;gt;` is the rightward assignment operator.&lt;/span&gt;
&lt;span class="n"&gt;obj_hash&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;last: &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is thanks to Ruby's pattern matching, which is actually a lot more flexible than JS destructuring. (For more complex examples, see &lt;a href="https://www.fullstackruby.dev/ruby-3-fundamentals/2021/01/06/everything-you-need-to-know-about-destructuring-in-ruby-3"&gt;"Everything You Need to Know About Destructuring in Ruby 3"&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;Note, however, that there is &lt;a href="https://github.com/tc39/proposal-pattern-matching"&gt;a proposal to add pattern matching to JS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object literals
&lt;/h3&gt;

&lt;p&gt;In JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Willard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;last&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wonka&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;full&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Ruby, every object has a class, so there's no concise way to define a one-off object, right?&lt;/p&gt;

&lt;p&gt;My first attempt to prove this wrong was to add a method to an &lt;code&gt;OpenStruct&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"ostruct"&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;first: &lt;/span&gt;&lt;span class="s2"&gt;"Willard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last: &lt;/span&gt;&lt;span class="s2"&gt;"Wonka"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Uh oh, that didn't work as intended!&lt;/span&gt;
&lt;span class="c1"&gt;# The `#full` method isn't actually defined.&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out this only works with a &lt;code&gt;Struct&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;first: &lt;/span&gt;&lt;span class="s2"&gt;"Willard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last: &lt;/span&gt;&lt;span class="s2"&gt;"Wonka"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Willard Wonka"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now we're nearly in the territory of an explicit class definition, far from a JS-style one-off object.&lt;/p&gt;

&lt;p&gt;OK, then just for fun, how about we expand &lt;code&gt;OpenStruct&lt;/code&gt; so that it actually does something with that block?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"ostruct"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenStruct&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_with_methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;open_struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;open_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;open_struct&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Now add a shortcut syntax.&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="ss"&gt;:[]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:create_with_methods&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Or, OpenStruct.create_with_methods(...)&lt;/span&gt;
&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;first: &lt;/span&gt;&lt;span class="s2"&gt;"Willard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last: &lt;/span&gt;&lt;span class="s2"&gt;"Wonka"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This still doesn't look as uniform as JS object literals, and performance-wise I'm sure Ruby is not optimized for this sort of object. That's because &lt;strong&gt;it goes against the grain of Ruby&lt;/strong&gt;, where &lt;em&gt;classes&lt;/em&gt; play a central role, as distinct from &lt;em&gt;instances&lt;/em&gt; of them. In JS, with its prototype-based object model, "classes" are syntactic sugar, and &lt;em&gt;individual objects&lt;/em&gt; are more central than in Ruby. (On how and why this is so, it's helpful to &lt;a href="https://auth0.com/blog/a-brief-history-of-javascript/"&gt;read about JS's early history&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;But we shouldn't overstate the difference: the JS and Ruby object models are actually similar in how dynamic both of them are. This makes Ruby-to-JS compilers like &lt;a href="https://opalrb.com/"&gt;Opal&lt;/a&gt; easier to implement, &lt;a href="https://www.reddit.com/r/ruby/comments/146damh/comment/jnqqe8u"&gt;according to an Opal maintainer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the end, learning more JS has given me a deeper appreciation of both JS &lt;em&gt;and&lt;/em&gt; Ruby: JS for the ingeniously simple idea behind its object model, and Ruby… for everything else 😄&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Learning Linux: recommended resources</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Mon, 30 Jan 2023 14:53:12 +0000</pubDate>
      <link>https://dev.to/fpsvogel/learning-linux-recommended-resources-heh</link>
      <guid>https://dev.to/fpsvogel/learning-linux-recommended-resources-heh</guid>
      <description>&lt;p&gt;Confession: until recently, I wasn't a fan of the command line.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What?! A whole year into your developer career, and you don't live 24/7 in the terminal????!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nope. But a few months ago I discovered what the whole rest of the world already knows: tab completion! 🙈 And that made me wonder: &lt;strong&gt;what other command-line superpowers am I missing out on?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I've embarked on a quest, a quest in which &lt;em&gt;(fade in epic music)&lt;/em&gt; I will learn the the ways of the command line, to wield the ancient caret. But I will not stop there. Nay, for it is a quest to a far-off land, a land nearly as ancient as Unix Time itself: the land of Linux—er, GNU/Linux.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Digression: At one point in the long &lt;a href="https://en.wikipedia.org/wiki/GNU/Linux_naming_controversy" rel="noopener noreferrer"&gt;GNU/Linux naming controversy&lt;/a&gt;, the name "LiGNUx" was proposed. Good thing **that&lt;/em&gt;* didn't catch on!*&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux learning resources
&lt;/h2&gt;

&lt;p&gt;I've heard that the gold standard Linux education is to install it, mess around with it until it breaks, and then repeat.&lt;/p&gt;

&lt;p&gt;But I like more structure in the beginning, so after Google-sleuthing for learning resources and crossing off some that I tried and didn't find useful, here the promising prospects still on the list. The items preceded by a dollar sign (💲) are not free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💲&lt;a href="https://nostarch.com/howlinuxworks3" rel="noopener noreferrer"&gt;How Linux Works&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linuxcommand.org/tlcl.php" rel="noopener noreferrer"&gt;The Linux Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlevy/the-art-of-command-line" rel="noopener noreferrer"&gt;The Art of Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2019/10/21/print-collection-of-my-first-7-zines/" rel="noopener noreferrer"&gt;Julia Evans - Your Linux Toolbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💲&lt;a href="https://wizardzines.com/zines/bite-size-pack/" rel="noopener noreferrer"&gt;Julia Evans - Bite Size zine pack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💲&lt;a href="https://wizardzines.com/zines/containers/" rel="noopener noreferrer"&gt;Julia Evans - How Containers Work&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💲&lt;a href="https://nostarch.com/wcss2" rel="noopener noreferrer"&gt;Wicked Cool Shell Scripts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://workingwithruby.com/wwup/intro" rel="noopener noreferrer"&gt;Jesse Storimer - Working with Unix Processes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list is excerpted from my learning roadmap, so &lt;a href="https://github.com/fpsvogel/learn-ruby#linux--command-line" rel="noopener noreferrer"&gt;check there&lt;/a&gt; for the latest version of the list.&lt;/p&gt;

&lt;p&gt;But reading books about Linux wouldn't do any good if I didn't get my hands dirty too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Linux on my old laptop
&lt;/h2&gt;

&lt;p&gt;I chose &lt;a href="https://linuxmint.com/" rel="noopener noreferrer"&gt;Linux Mint&lt;/a&gt; over &lt;a href="https://ubuntu.com/" rel="noopener noreferrer"&gt;Ubuntu&lt;/a&gt; because I like Mint's more familiar UI, and the fact that it's community-supported makes me feel superior 😏&lt;/p&gt;

&lt;p&gt;I didn't run into any major installation problems, and all my hardware works. Impressive! That certainly wasn't the case the last time I tried Linux, but that was 15 years ago when I was a wee wannabe hacker. (Now I'm an &lt;em&gt;adult&lt;/em&gt; wannabe hacker.)&lt;/p&gt;

&lt;p&gt;So yeah… not much to say here because it was so easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goodbye to Windows?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fpsvogel.com/posts/2020/windows-terminal" rel="noopener noreferrer"&gt;I've already confessed&lt;/a&gt; that I use Windows on my personal computer, and I use my non-optional work MacBook &lt;a href="https://fpsvogel.com/posts/2022/switching-from-pc-windows-to-macos" rel="noopener noreferrer"&gt;in a way as Windows-like as possible&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But recently I listed in my mind the things I like about Windows: customizability, lots of free apps, and a real Linux command line in WSL which gives me fewer headaches than the MacOS terminal.&lt;/p&gt;

&lt;p&gt;Then it hit me: &lt;strong&gt;all of these points are even more true in Linux&lt;/strong&gt;. So why not switch to Linux?&lt;/p&gt;

&lt;p&gt;I want to switch, but the only snag is that I &lt;strong&gt;love&lt;/strong&gt; &lt;a href="https://www.autohotkey.com/" rel="noopener noreferrer"&gt;AutoHotkey&lt;/a&gt;, and a Linux port is still &lt;a href="https://github.com/phil294/AHK_X11" rel="noopener noreferrer"&gt;in progress&lt;/a&gt;. I use AutoHotkey &lt;a href="https://fpsvogel.com/posts/2020/autohotkey" rel="noopener noreferrer"&gt;to make my keyboard and (especially) my mouse more useful&lt;/a&gt;. I could probably do the same things by other means in Linux—everything is customizable in Linux, after all. And anyway I might use my mouse less and less as I become more capable with the terminal. Still, I'll wait a while in the hope that I'll be able to more or less plop my AutoHotkey script into the Linux version.&lt;/p&gt;

&lt;p&gt;But it's only a matter of time before I switch. So I guess it's appropriate to end with a non-ending.&lt;/p&gt;

&lt;p&gt;✨ &lt;em&gt;TO BE CONTINUED (SOMEDAY)…&lt;/em&gt; ✨&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>crypto</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Learning Git: my favorite resources</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Wed, 18 Jan 2023 15:40:08 +0000</pubDate>
      <link>https://dev.to/fpsvogel/learning-git-my-favorite-resources-45bi</link>
      <guid>https://dev.to/fpsvogel/learning-git-my-favorite-resources-45bi</guid>
      <description>&lt;p&gt;When you're making a pull request, do you ever realize your Git branch has become a &lt;strong&gt;massive pile of disordered and unrelated changes&lt;/strong&gt;, but then you don't do anything about it because you're afraid of making an even worse mess by fooling around with Git commands that you &lt;strong&gt;don't understand&lt;/strong&gt; or even &lt;strong&gt;know how to undo&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Me? No, of course not…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dw6s5qsnf4g8ba17cau.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5dw6s5qsnf4g8ba17cau.gif" alt="Nervous laughter" width="400" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, I've decided to improve my Git skills, and I thought I'd share my favorite learning resources that I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  My favorite Git learning resources
&lt;/h2&gt;

&lt;p&gt;These are excerpted from &lt;a href="https://github.com/fpsvogel/learn-ruby#git"&gt;the new "Git" section&lt;/a&gt; of my "Learn Ruby" list. &lt;em&gt;(Yeah I know, Git is not part of Ruby. It's just that I didn't want to create a separate list for general programming skills.)&lt;/em&gt; I keep the list up to date, so unless you're reading this in January 2023, you should check there in case I've found more Git resources.&lt;/p&gt;

&lt;p&gt;I made the Git list by (1) scouring the Web for recommended resources, then (2) trying out each one to see if it would be worth going through to the end. In case you're curious about which resources &lt;em&gt;didn't&lt;/em&gt; make the cut, &lt;a href="https://github.com/fpsvogel/learn-ruby/commit/adc0385717160935bf233f98fc7e9686b023e283"&gt;here's the commit where they are removed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(Just so you know, I'm &lt;strong&gt;&lt;em&gt;proud&lt;/em&gt;&lt;/strong&gt; of that commit. Originally I made separate commits to remove each unwanted resource, interspersed with other unrelated commits. But at the end I used my new Git skills to do an interactive rebase, squashing a bunch of commits into one and re-ordering the unrelated commits to an earlier point in the branch 😎)&lt;/p&gt;

&lt;p&gt;Without further ado, here are my favorite Git learning resources so far. They're all free except for the last one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basics:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ohmygit.org/"&gt;Oh My Git!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ohshitgit.com/"&gt;Oh Shit, Git!?!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eficode-academy/git-katas"&gt;Git Katas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intermediate/advanced:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/book"&gt;Pro Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/rebuilding-git-in-ruby"&gt;Thoughtbot - Rebuilding Git in Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💲&lt;a href="https://shop.jcoglan.com/building-git"&gt;Building Git&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now I'm working through Git Katas. After that, and before going through the intermediate/advanced Git resources, I might spend time on other fundamentals (see below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Git-related VS Code extensions
&lt;/h2&gt;

&lt;p&gt;Until recently I used the &lt;a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens"&gt;GitLens&lt;/a&gt; VS Code extension, but then I realized that its only features that I care about are either &lt;em&gt;finnicky&lt;/em&gt; (popup-on-hover Git blame), &lt;em&gt;needlessly complicated&lt;/em&gt; (browsing a file's history), or &lt;em&gt;behind a paywall&lt;/em&gt; (graph visualizer).&lt;/p&gt;

&lt;p&gt;So I replaced GitLens with these smaller extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=huizhou.githd"&gt;Git History Diff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=waderyan.gitblame"&gt;Git Blame&lt;/a&gt; to quickly see the current line's blame in the status bar.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph"&gt;Git Graph&lt;/a&gt; for when your repo is complex enough that &lt;code&gt;git log --oneline --graph&lt;/code&gt; is hard to decipher.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reflections on the fundamentals
&lt;/h2&gt;

&lt;p&gt;Besides the "Git" section, I've added three other sections under a "Fundamental tools" heading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fpsvogel/learn-ruby#sql"&gt;SQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fpsvogel/learn-ruby#how-the-internet-works"&gt;How the internet works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fpsvogel/learn-ruby#linux--command-line"&gt;Linux / command line&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm really enjoying learning these fundamentals. I love Ruby, but it's nice to focus my learning on something else for a change—though I'm still watching &lt;a href="https://github.com/fpsvogel/learn-ruby#ruby-blogs-podcasts-screencasts"&gt;Ruby screencasts&lt;/a&gt; whenever my wrists need a break.&lt;/p&gt;

&lt;p&gt;In fact, I'm having so much fun that I'm tempted to expand the "Fundamental tools" section, but it's probably best to pick a few technologies and go deep into each, rather than throwing a bunch more stuff in there and making it less likely that I'll go deep into anything.&lt;/p&gt;

&lt;p&gt;So, as a compromise, I have a separate &lt;a href="https://github.com/fpsvogel/learn-ruby#expanding-my-horizons"&gt;"Expanding my horizons"&lt;/a&gt; section for those times in the future when I want to take a dive into something outside of Ruby or frontend skills.&lt;/p&gt;

&lt;p&gt;As I write this, I'm reminded of &lt;a href="https://thoughtbot.com/blog/what-technologies-should-i-learn"&gt;a Thoughtbot blog post&lt;/a&gt; about "evergreen skills" (similar to what I've called "fundamental tools") which is worth a read.&lt;/p&gt;

&lt;p&gt;I hope my explorations of Git and other fundamentals has been as rewarding for you as it has been for me. Happy learning!&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Learning Ruby: a retrospective</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Fri, 06 Jan 2023 16:39:49 +0000</pubDate>
      <link>https://dev.to/fpsvogel/learning-ruby-a-retrospective-42d6</link>
      <guid>https://dev.to/fpsvogel/learning-ruby-a-retrospective-42d6</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;But why Ruby?&lt;/li&gt;
&lt;li&gt;
My "Learning Ruby" roadmap

&lt;ul&gt;
&lt;li&gt;In the beginning&lt;/li&gt;
&lt;li&gt;Time lapse&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
What next? Keeping it simple
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;Happy New Year! I hadn't planned on any New Year's resolutions, but then I came across &lt;a href="https://bringback.blog/" rel="noopener noreferrer"&gt;Bring Back Blogging&lt;/a&gt;, a challenge to publish three posts in January. I just couldn't resist. So my resolution is to write more blog posts… at least this month 😂&lt;/p&gt;

&lt;p&gt;Today I'll reflect on my past two years of learning Ruby. It seems appropriate, this being my first new year as a professional Ruby developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  But why Ruby?
&lt;/h2&gt;

&lt;p&gt;Why did I say I'm a &lt;em&gt;Ruby&lt;/em&gt; developer just now? Why not a &lt;em&gt;software&lt;/em&gt; or &lt;em&gt;web&lt;/em&gt; developer? Am I one of those &lt;a href="https://www.reddit.com/r/ruby/comments/ym86p5/linus_torvalds_the_ruby_people_strange_people/" rel="noopener noreferrer"&gt;strange Ruby people&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Of course&lt;/em&gt; I'm a software developer and a web developer. I've learned lots besides Ruby, and this year I aim to become better-versed in web standards like web components and the latest CSS features.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;generally I have the most fun when I'm writing Ruby&lt;/strong&gt;, and Ruby is where I've spent the most time. When I started learning programming in 2020, I spent the first few months on web basics but then I switched my focus to Ruby, and that's where I've happily been ever since.&lt;/p&gt;

&lt;h2&gt;
  
  
  My "Learning Ruby" roadmap
&lt;/h2&gt;

&lt;p&gt;As a guide to my reflections today, I'll use &lt;a href="https://github.com/fpsvogel/learn-ruby" rel="noopener noreferrer"&gt;my "Learning Ruby" roadmap&lt;/a&gt;, which originally arose out of the chaos of my bookmark hell, where I was having trouble keeping track of the actually important learning resources. The roadmap worked well for me and eventually I put it up on GitHub because making it public gives me more motivation to keep making progress.&lt;/p&gt;

&lt;p&gt;But another benefit to its being on GitHub (besides the warm fuzzies I get when a random stranger stars it) is that now, almost two years later, I can look back at earlier versions and see how my learning goals evolved.&lt;/p&gt;

&lt;p&gt;What did my roadmap look like in the first Git commit? Let's go back in time to May 2021…&lt;/p&gt;

&lt;h3&gt;
  
  
  In the beginning
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/fpsvogel/learn-ruby/tree/d7ac9571f039cdc633de2c46f905277aab788661" rel="noopener noreferrer"&gt;The earliest tracked version&lt;/a&gt; of my roadmap was pretty simple. It had three sections of resources: frontend basics, Ruby, and computer science.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/fpsvogel/learn-ruby/tree/e07a73b6d651b5653e7813127dfc73f220799c04" rel="noopener noreferrer"&gt;My roadmap today&lt;/a&gt; &lt;em&gt;still&lt;/em&gt; starts with frontend basics and ends with computer science. &lt;strong&gt;It's the middle part that has exploded:&lt;/strong&gt; instead of one Ruby section, I now have &lt;em&gt;seven&lt;/em&gt; Ruby-related sections, in addition to four other new sections.&lt;/p&gt;

&lt;p&gt;But back to my early roadmap, the emphasis is clear: &lt;strong&gt;I loved Ruby&lt;/strong&gt; and &lt;strong&gt;I thought all the important stuff could fit under the Ruby heading&lt;/strong&gt;, except for my "healthy dose of computer science" (as I put it) and a smattering of frontend knowledge, which got their own sections.&lt;/p&gt;

&lt;p&gt;At the same time, &lt;strong&gt;I was conscious that Ruby was not the most popular choice for a beginner&lt;/strong&gt;, and thus "Objections" was the first preliminary section of my roadmap, and the first objection was &lt;em&gt;"Why Ruby??"&lt;/em&gt; I eventually scrapped the "Objections" section because it made my roadmap &lt;em&gt;seem&lt;/em&gt; more objectionable than it actually is, but later that year I expanded my answer to "Why Ruby?" into a blog post, &lt;a href="https://fpsvogel.com/posts/2021/why-learn-ruby" rel="noopener noreferrer"&gt;"Ruby for the self-taught developer"&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time lapse
&lt;/h3&gt;

&lt;p&gt;A couple months passed. I finally gave in and &lt;a href="https://github.com/fpsvogel/learn-ruby/tree/4c1a8af840dbd1d1355bf5eef1192eafe2e1e8a0" rel="noopener noreferrer"&gt;split up the "Ruby" section&lt;/a&gt; as I geared up to learn Rails. Over the following year, the new "Rails" section &lt;a href="https://github.com/fpsvogel/learn-ruby/tree/ba820a63b0686974c0648e8bce3c4190293cd77b#rails" rel="noopener noreferrer"&gt;would go on to expand&lt;/a&gt; to massive size and extent, containing even a "PostgreSQL" subsection 🙈&lt;/p&gt;

&lt;p&gt;It was in mid- to late-2021 that I first sighted frontiers in Rubyland which even now I'm pushing myself to start exploring, such as reading open-source Rails codebases (&lt;a href="https://github.com/fpsvogel/learn-ruby/commit/ecce26bdfc29a3fdf231a89fc37420bb8432c094#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5R83-R93" rel="noopener noreferrer"&gt;the early version&lt;/a&gt; and &lt;a href="https://github.com/fpsvogel/learn-ruby/tree/e07a73b6d651b5653e7813127dfc73f220799c04#rails-codebases-to-study" rel="noopener noreferrer"&gt;the expanded list of today&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;My studies slowed down a bit in 2022, for the happy reason that I'd gotten my first developer job and I was doing more of my growing &lt;em&gt;away&lt;/em&gt; from my roadmap.&lt;/p&gt;

&lt;p&gt;In July I took the momentous step of &lt;a href="https://github.com/fpsvogel/learn-ruby/commit/945e990095025e6e18e87b31654cb44b11511d2c" rel="noopener noreferrer"&gt;adding a "JavaScript" section&lt;/a&gt;. Its earliest entry, appropriately enough, is titled "You Don't Know JS" 😂&lt;/p&gt;

&lt;p&gt;In September I finally &lt;a href="https://github.com/fpsvogel/learn-ruby/commit/9875e75e218598d5a0bd06989d782e86392d4581" rel="noopener noreferrer"&gt;split up the giant "Rails" section&lt;/a&gt; into standalone sections which are more sensible, if a bit overwhelming in the table of contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next? Keeping it simple
&lt;/h2&gt;

&lt;p&gt;As I looked through my commit history just now, I noticed a pattern: a section of my roadmap grows and grows until it fragments into a profusion of new sections, one or two of which grow again and repeat the process.&lt;/p&gt;

&lt;p&gt;On the other hand, one of the stated purposes of my roadmap is &lt;strong&gt;to help beginners who too often feel lost amid a sea of tutorials without clear direction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I'll have to be disciplined this year to make sure my roadmap doesn't get out of hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep revising the "done" sections.&lt;/strong&gt; I'll need to keep putting on my me-two-years-ago mindset and tightening up the "done" part of my roadmap, because beginners tend to prefer being handed one or two excellent resources over having to choose from all twenty options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep making progress.&lt;/strong&gt; The other scenario in which my roadmap gets shorter is when I actually put in the time to study, checking things off and deciding whether they're worth keeping on the roadmap (usually they're not).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut out the less important stuff.&lt;/strong&gt; For example, my progress in the computer science section has stalled as I've realized that knowing how to implement data structures and algorithms from scratch makes no discernible difference in how I code. So should I get rid of the computer science section, or keep it there for later? I'll have to think about it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for going on this reflective blast to the past with me 🚀&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>OOP vs. services for organizing business logic: is there a third way?</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Tue, 06 Dec 2022 16:17:15 +0000</pubDate>
      <link>https://dev.to/fpsvogel/oop-vs-services-for-organizing-business-logic-is-there-a-third-way-g3d</link>
      <guid>https://dev.to/fpsvogel/oop-vs-services-for-organizing-business-logic-is-there-a-third-way-g3d</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;The good old days&lt;/li&gt;
&lt;li&gt;Then along came Rails&lt;/li&gt;
&lt;li&gt;Two philosophical camps?&lt;/li&gt;
&lt;li&gt;Service object skepticism&lt;/li&gt;
&lt;li&gt;
Second-guessing myself; more study needed

&lt;ul&gt;
&lt;li&gt;Deductive study: books, talks, and gems&lt;/li&gt;
&lt;li&gt;Inductive study: open-source Rails codebases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Conclusion: to be continued…
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: In this blog post I raise many questions and give few answers. At the bottom I list resources which I'm exploring in search of an answer, so skip down if that's all you care about.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business logic.&lt;/strong&gt; Everyone has it, and no one seems to agree on where to put it in a Rails app. Some people stuff it all in Active Record models, others throw it out into service objects, and still others put it in POROs. (But then &lt;a href="https://twitter.com/JacobDaddario/status/1578083937787707392"&gt;where do you put the POROs&lt;/a&gt;?)&lt;/p&gt;

&lt;p&gt;In all these debates, there's probably an element of different answers coming from different needs: people who work with &lt;strong&gt;small apps&lt;/strong&gt; don't stray far from The Rails Way of MVC (models, views, and controllers), whereas those who work with &lt;strong&gt;larger apps&lt;/strong&gt; might feel the need for a more sophisticated architecture.&lt;/p&gt;

&lt;p&gt;That being said, I sense that these disagreements also reflect a more fundamental question: &lt;strong&gt;&lt;em&gt;How should the app interact with the database?&lt;/em&gt;&lt;/strong&gt; Or in other words, should database tables be near the surface, &lt;strong&gt;&lt;em&gt;or&lt;/em&gt;&lt;/strong&gt; should we put in the effort to hide the data model that is reflected in database tables?&lt;/p&gt;

&lt;p&gt;I may have lost you already, so before I wade too deep into philosophy, let tell the story of why I'm struggling with these questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good old days
&lt;/h2&gt;

&lt;p&gt;Before I learned Rails, I knew Ruby. I loved it. It made sense. Propelled by Sandi Metz's talks and books, I could write a plain Ruby app in the most beautiful and satisfying OOP style. Life was good.&lt;/p&gt;

&lt;p&gt;But I knew I couldn't linger in those enchanted woods forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then along came Rails
&lt;/h2&gt;

&lt;p&gt;I learned Rails and got my first programming job working on a Rails app of over two hundred thousand lines of Ruby code, plus React views. &lt;strong&gt;Suddenly things didn't make so much sense anymore.&lt;/strong&gt; I often didn't (and still don't) know where a piece of code belongs. Let's even set aside React views and the duplication of backend logic that I find hard to resist when writing a React view. Let's focus &lt;strong&gt;only on backend Ruby code&lt;/strong&gt;: &lt;em&gt;even there&lt;/em&gt; I find myself indecisive when trying to decide where to put a new piece of code.&lt;/p&gt;

&lt;p&gt;The most convenient place for that new bit of code is an existing Active Record model, but when I'm crawling through a huge model I'm reminded that maybe I should think hard about where to put this code. So I turn to alternative places, but then I'm faced with a jungle of service objects and variously-located POROs 😵‍💫&lt;/p&gt;

&lt;p&gt;I usually find a tolerable solution, but in the end I always wonder: where does business logic really belong? 🤔&lt;/p&gt;

&lt;h2&gt;
  
  
  Two philosophical camps?
&lt;/h2&gt;

&lt;p&gt;As I looked through discussions of this question in the Ruby community, I noticed that most answers came from one of two "sides": &lt;strong&gt;advocates&lt;/strong&gt; and &lt;strong&gt;opponents&lt;/strong&gt; of service objects. In reality it's a bit more nuanced than that: advocates might propose a pattern that is a more sophisticated version of service objects, and many opponents admit that careful OOP design is important to augment Rails' MVC structure.&lt;/p&gt;

&lt;p&gt;But the reason I lump them into two camps is that &lt;strong&gt;each has a different approach&lt;/strong&gt; to the fundamental question I posed earlier: &lt;strong&gt;&lt;em&gt;How should the app interact with the database?&lt;/em&gt;&lt;/strong&gt; In the context of Rails, this question can be rephrased like this: &lt;strong&gt;&lt;em&gt;What should an Active Record model represent?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Advocates of service objects often &lt;a href="https://youtu.be/CRboMkFdZfg?t=319"&gt;think of Active Record models as &lt;strong&gt;models of database tables&lt;/strong&gt;&lt;/a&gt;, and therefore not an appropriate place to put business logic. The other camp sees Active Record models as &lt;strong&gt;models of domain objects&lt;/strong&gt; that just happen to be backed by a database table, and therefore a perfectly suitable place for business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service object skepticism
&lt;/h2&gt;

&lt;p&gt;For several months I thought the anti-service-object camp was right, end of discussion. It seemed clear to me that Active Record models are intended to be domain models:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. It's spelled out in the Rails Guides.
&lt;/h4&gt;

&lt;p&gt;From the section &lt;a href="https://guides.rubyonrails.org/active_record_basics.html#what-is-active-record-questionmark"&gt;"What is Active Record?"&lt;/a&gt;(emphasis mine):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Active Record is the M in MVC - the model - which is the layer of the system responsible for &lt;strong&gt;representing business data and logic&lt;/strong&gt;. Active Record facilitates the creation and use of &lt;strong&gt;business objects&lt;/strong&gt; whose data requires persistent storage to a database."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And, shortly afterward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In Active Record, objects carry &lt;strong&gt;both persistent data and behavior which operates on that data&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  2. Martin Fowler, who first described the Active Record pattern, agrees.
&lt;/h4&gt;

&lt;p&gt;To quote &lt;a href="https://www.martinfowler.com/eaaCatalog/activeRecord.html"&gt;his article on the Active Record pattern&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"An object carries both data and behavior. Much of this data is persistent and needs to be stored in a database. Active Record uses the most obvious approach, putting data access logic in the domain object."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So an Active Record object is intended to be fundamentally a domain object, with database access added for convenience, not the other way around. Probably that's why it seems against the grain of Rails when service objects are &lt;strong&gt;&lt;em&gt;the&lt;/em&gt;&lt;/strong&gt; place where business logic goes.&lt;/p&gt;

&lt;p&gt;Fowler directly criticizes service objects in &lt;a href="https://www.martinfowler.com/bliki/AnemicDomainModel.html"&gt;his article on anemic domain models&lt;/a&gt;. In reference to putting domain logic in services, he says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you've robbed yourself blind."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  3. Conversely, domain models don't have to be Active Record models; they can be PORO models.
&lt;/h4&gt;

&lt;p&gt;Taking advantage of this can alleviate many of the "fat model" problems that service objects seek to solve.&lt;/p&gt;

&lt;p&gt;Martin Fowler &lt;a href="https://gist.github.com/blaix/5764401"&gt;proposes refactoring a service object into a PORO&lt;/a&gt;, and he's not alone: some in the Ruby community have written the same (&lt;a href="https://www.codewithjason.com/code-without-service-objects/"&gt;1&lt;/a&gt;, &lt;a href="https://www.fullstackruby.dev/object-orientation/rails/2018/03/06/why-service-objects-are-an-anti-pattern#concerns-and-poros-are-your-friends"&gt;2&lt;/a&gt;, &lt;a href="https://alexbarret.com/blog/2020/service-object-alternative"&gt;3&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;There are lots of patterns that can be used in POROs around Active Record models. For example, if a record is created from complex form input, you could use &lt;a href="https://thoughtbot.com/blog/activemodel-form-objects"&gt;a form object&lt;/a&gt; instead of a service object.&lt;/p&gt;

&lt;p&gt;Also, some versions of service objects &lt;strong&gt;&lt;em&gt;are&lt;/em&gt;&lt;/strong&gt; somewhat object-oriented when &lt;a href="https://youtu.be/CRboMkFdZfg?t=1576"&gt;they reject the notion that service objects should have only a &lt;code&gt;#call&lt;/code&gt; method&lt;/a&gt; and when &lt;a href="https://youtu.be/CRboMkFdZfg?t=1322"&gt;they share code within the same class&lt;/a&gt;. In these cases, a service object is a bit more like a purpose-built PORO.&lt;/p&gt;

&lt;p&gt;So why not just take the next step and put these services in the &lt;code&gt;app/models&lt;/code&gt; folder, and refactor them from procedures into actual domain models? To take an example from the last link above: &lt;code&gt;SalesTeamNotifier.send_daily_notifications&lt;/code&gt; could be changed to &lt;code&gt;Internal::Notification.new(receiver: 'sales').send&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So yeah, &lt;strong&gt;I was a convinced service object skeptic&lt;/strong&gt;, firm in dismissing even the need for anything but classic OOP. When I tried to be fair and play devil's advocate, I only got as far as conceding that OOP is harder to get right than procedures, and OOP done wrong can result in a lot of moving parts and less clarity about what actually happens when. I could even appreciate the simplicity of services, in the sense that making one is as easy as copy-pasting a long model method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second-guessing myself; more study needed
&lt;/h2&gt;

&lt;p&gt;Fast forward a few months. I still don't like service objects, and I still like OOP. But now I'm less certain that the cure-all for badly organized business logic is "just do more OOP, end of story."&lt;/p&gt;

&lt;p&gt;After all, if so many people &lt;strong&gt;feel the need&lt;/strong&gt; for service objects, and if OOP is evidently &lt;strong&gt;so hard to get right&lt;/strong&gt;, aren't these signs that &lt;strong&gt;something&lt;/strong&gt; is missing? &lt;strong&gt;&lt;em&gt;Maybe&lt;/em&gt;&lt;/strong&gt; that missing something is just better OOP, but in that case good OOP is hard to come by and we at least need a more accessible way to do it.&lt;/p&gt;

&lt;p&gt;So I've set out to explore the problem of organizing business logic from more angles than before, using the resources listed below. These lists are excerpted from &lt;a href="https://github.com/fpsvogel/learn-ruby"&gt;my "Learning Ruby" road map&lt;/a&gt; which I often update, so you may want to find these lists there if this post is old at the time of your reading it. The sections corresponding to the lists below are, at the time of writing, &lt;a href="https://github.com/fpsvogel/learn-ruby#rails-architecture"&gt;"Rails architecture"&lt;/a&gt; and &lt;a href="https://github.com/fpsvogel/learn-ruby#rails-codebases-to-study"&gt;"Rails codebases"&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deductive study: books, talks, and gems
&lt;/h3&gt;

&lt;p&gt;Here are some resources that I hope will shed light on the question of organizing business logic better, both in terms of solutions &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; in terms of &lt;strong&gt;when (under what conditions) these alternative approaches are beneficial&lt;/strong&gt; as opposed to simple OOP with Rails defaults. This list is not exhaustive; in particular I've omitted gems that are just a service object implementation. Some of these resources are closely related to service objects, but that's intentional–I'm compensating for my bias against them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain-Driven Design&lt;/strong&gt;, which aims to augment OOP to prevent problems such as fat models. It's intended for large, complex domains. Resources: &lt;a href="https://www.youtube.com/watch?v=Q_0XW46IlHY"&gt;"Getting modules right with Domain-driven Design"&lt;/a&gt; (talk), &lt;a href="https://www.oreilly.com/library/view/learning-domain-driven-design/9781098100124/"&gt;&lt;em&gt;Learning Domain-Driven Design&lt;/em&gt;&lt;/a&gt; (book).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other approaches&lt;/strong&gt; that are more lightweight and have some of the same goals:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://solnic.podia.com/data-oriented-web-development-with-ruby"&gt;&lt;em&gt;Data Oriented Web Development with Ruby&lt;/em&gt;&lt;/a&gt; (upcoming book) by Peter Solnica, who is on the &lt;a href="https://hanamirb.org/"&gt;Hanami&lt;/a&gt; core team. Learning Hanami wouldn't be a bad idea either.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://leanpub.com/maintain-rails"&gt;&lt;em&gt;Maintainable Rails&lt;/em&gt;&lt;/a&gt; (book), which uses gems that are part of the Hanami ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nts.strzibny.name/business-logic-in-rails-with-contexts/"&gt;"Organizing business logic in Rails with contexts"&lt;/a&gt; (blog post).&lt;/li&gt;
&lt;li&gt;Learn more about the repository pattern: &lt;a href="https://engineering.solarisbank.com/the-repository-pattern-in-ruby-with-the-active-record-library-f0445fa282c"&gt;article&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=36LB8bfEeVc"&gt;talk&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relevant gems&lt;/strong&gt; that seem worth learning from:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dry-rb.org/gems/dry-transaction"&gt;dry-transaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/collectiveidea/interactor"&gt;Interactor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sequent.io/"&gt;Sequent&lt;/a&gt; – CQRS and event sourcing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/RailsEventStore/rails_event_store"&gt;Rails Event Store&lt;/a&gt; – for an event-driven architecture&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kigster/ventable"&gt;Ventable&lt;/a&gt; – a variation of the Observer design pattern&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/krisleech/wisper"&gt;Wisper&lt;/a&gt; – the Publish-Subscribe design pattern&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Shopify/packwerk"&gt;Packwerk&lt;/a&gt; – to enforce boundaries and modularize Rails applications&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Inductive study: open-source Rails codebases
&lt;/h3&gt;

&lt;p&gt;I rarely read a lot of code outside of work, but I plan to change that. Below are Rails projects that I've seen mentioned more than once as good examples to learn from, &lt;strong&gt;&lt;em&gt;or&lt;/em&gt;&lt;/strong&gt; they are sufficiently active and well-known as to be good candidates for study.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small codebases:&lt;/strong&gt; Less than 50k lines of Ruby code.

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/codetriage/codetriage"&gt;github.com/codetriage/codetriage&lt;/a&gt; (6k lines): &lt;em&gt;Issue tracker for open-source projects.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/joemasilotti/railsdevs.com"&gt;github.com/joemasilotti/railsdevs.com&lt;/a&gt; (12k lines): &lt;em&gt;The reverse job board for Ruby on Rails developers.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lobsters/lobsters"&gt;github.com/lobsters/lobsters&lt;/a&gt; (13k lines): &lt;em&gt;Hacker News clone.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/thoughtbot/upcase"&gt;github.com/thoughtbot/upcase&lt;/a&gt; (14k lines): &lt;em&gt;Learning platform for developers.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/houndci/hound"&gt;github.com/houndci/hound&lt;/a&gt; (14k lines): &lt;em&gt;Automated code review for GitHub PRs.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rubygems/rubygems.org"&gt;github.com/rubygems/rubygems.org&lt;/a&gt; (26k lines): &lt;em&gt;Where Ruby gems are hosted.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger codebases:&lt;/strong&gt; More than 50k lines of Ruby code.

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/solidusio/solidus"&gt;github.com/solidusio/solidus&lt;/a&gt; (72k lines): &lt;em&gt;E-commerce platform.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mastodon/mastodon"&gt;github.com/mastodon/mastodon&lt;/a&gt; (75k lines): &lt;em&gt;Like Twitter but self-hosted and federated.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/forem/forem"&gt;github.com/forem/forem&lt;/a&gt; (103k lines): &lt;em&gt;Powers the blogging site &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt;.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/alphagov/whitehall"&gt;github.com/alphagov/whitehall&lt;/a&gt; (117k lines): &lt;em&gt;Publishes government content on &lt;a href="https://www.gov.uk/"&gt;gov.uk&lt;/a&gt;.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/discourse/discourse"&gt;github.com/discourse/discourse&lt;/a&gt; (322k lines): &lt;em&gt;Discussion forum platform.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/instructure/canvas-lms"&gt;github.com/instructure/canvas-lms&lt;/a&gt; (745k lines): &lt;em&gt;A popular LMS (learning management system).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/gitlab"&gt;gitlab.com/gitlab-org/gitlab&lt;/a&gt; (1.8 million lines): &lt;em&gt;Like GitHub but with CI/CD and DevOps features built in. Has great &lt;a href="https://docs.gitlab.com/ee/development/architecture.html"&gt;docs on architecture&lt;/a&gt;.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: to be continued…
&lt;/h2&gt;

&lt;p&gt;In a year or two I may be able to give something more like an answer to the questions I've raised here. For now, I've made a start by processing my thoughts and mapping out some promising resources.&lt;/p&gt;

&lt;p&gt;What about you? Have you had any moments of insight into how to organize business logic? Do you know of a great resource not mentioned here? I'd love to hear about it!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>architecture</category>
      <category>oop</category>
    </item>
    <item>
      <title>The first six months: lessons learned as a junior developer</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Wed, 24 Aug 2022 19:04:00 +0000</pubDate>
      <link>https://dev.to/fpsvogel/the-first-six-months-lessons-learned-as-a-junior-developer-413h</link>
      <guid>https://dev.to/fpsvogel/the-first-six-months-lessons-learned-as-a-junior-developer-413h</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Find balance&lt;/li&gt;
&lt;li&gt;Keep up your learning journey&lt;/li&gt;
&lt;li&gt;Don't be a silo: connect more, communicate better&lt;/li&gt;
&lt;li&gt;
Conclusion
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;I've reached a milestone: six months into my first developer job 🎉 Time for some reflection! What have I learned? If I were to give advice to myself six months ago, what wisdom would I impart? Here are a few highlights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find balance
&lt;/h2&gt;

&lt;p&gt;Learning the ropes as a junior developer can be exhausting, especially if you have a competitive "gotta get tons done" personality like I do. Impressing your peers and exceeding your manager's expectations feels so good that it's hard to slow down.&lt;/p&gt;

&lt;p&gt;But the cost is real, as I've been painfully reminded. A couple times already I've gotten into a cycle of overwork ⇨ burnout ⇨ lethargic exhaustion ⇨ panic ⇨ repeat. More recently, here are some ways I've found to stay out of that unpleasant cycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give pessimistic time estimates.&lt;/strong&gt; When your manager asks how much time you'll need to finish a project, resist the fear of giving an embarrassingly long estimate, because what you think is a long time probably isn't. Expect unexpected delays. After I was burned by a short deadline of my own making, a more senior colleague suggested that I add in a 1.5–2x "contingency factor" to my estimates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put rest into your work rhythm.&lt;/strong&gt; Despite my best attempts, I've found it impossible to work at a completely steady pace. (Even &lt;a href="https://www.howtogeek.com/322433/four-simple-timers-that-remind-you-to-take-breaks-from-your-computer"&gt;break timers&lt;/a&gt; don't work for me, because I tend to cancel the pauses or close the app entirely if I'm "in the zone" and don't want to be interrupted.) I've accepted that there will always be crunch times when I need to do a lot of focused work, and there will always be slower times when not as much needs to be done right now. Rather than stressing about how to flatten that up-and-down, I've found it more helpful to take advantage of the slow times, using them as a chance to rest. Don't immediately pile on more work when you've just stretched yourself to finish something. Attune yourself to the ebb and flow, and look for chances to step away from the computer, quiet your inner restlessness, and &lt;em&gt;rest&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain healthy habits.&lt;/strong&gt; On the other hand, it's good to keep up a healthy routine even in the busiest times. Find a few things that help you relax and stay happy, and don't beat yourself up if you don't get around to doing all of them every day. For me these things include:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Reading.&lt;/em&gt; &lt;a href="https://dev.to/reading"&gt;I love reading&lt;/a&gt; because a good book gives me energy and relaxation at the same time, even while expanding my horizons. Try to find a hobby that does that for you.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Latin.&lt;/em&gt; A more social hobby for me is spoken Latin. (Yes, &lt;a href="https://www.youtube.com/watch?v=xj-zCfVC2Zg"&gt;that's a thing&lt;/a&gt;.) Whatever it is for you, regularly doing something fun in a group can provide a refreshing change of pace from your usual day-to-day.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Mindfulness.&lt;/em&gt; The &lt;a href="https://www.balanceapp.com/"&gt;Balance&lt;/a&gt; app is great, and free for the first year. I rarely &lt;em&gt;enjoy&lt;/em&gt; mindfulness, but it helps me avoid bad thought patterns and appreciate the little things in life.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Building up relationships.&lt;/em&gt; For me this means spending more quality time with my wife—not just watching more Netflix, but trying new things together (this summer it was camping), and making an effort to listen more and be a better companion. I've also been reconnecting with old friends. It takes effort to do all this, but there's no replacement for the love of family and friends.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Going on walks.&lt;/em&gt; The exercise makes me feel good, and it's another chance to talk with my wife, or if I'm alone, to listen to audiobooks and podcasts 🤓 But it's also nice to take off the headphones once in a while and just look at the trees.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lying in bed daydreaming.&lt;/em&gt; I'm not kidding! Even a short 10-minute burst can be very refreshing. To get there &lt;a href="https://www.nytimes.com/2021/04/10/at-home/daydreaming.html"&gt;you might have to retrain your imagination&lt;/a&gt;, but it's worth the effort.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep up your learning journey
&lt;/h2&gt;

&lt;p&gt;For two years before I got this job, I learned programming on the side while working a "pay the bills" type of job. It was a huge relief to put that stage behind me, but a few months into my new job I found myself missing the freedom and adventure that I felt when I was learning programming.&lt;/p&gt;

&lt;p&gt;Thankfully it was an easy fix, because I was the one holding myself back. Juniors at my company are encouraged to take time to deepen their skills, apart from product work; I just wasn't taking the time. Part of the problem was that I simply needed to slow down (see above). As soon as I did that, it became easier to spend an hour here and there on learning and deepening my skills.&lt;/p&gt;

&lt;p&gt;I've found a few other practices useful in maintaining a learning habit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Set goals.&lt;/strong&gt; Identify one or two areas where you want to improve, and focus on those. For me it helps that I already have lots to choose from in &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs"&gt;my "Learning Ruby" list&lt;/a&gt;, which I've been building up for the past two years.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Keep an open mind.&lt;/strong&gt; Even as a junior it's easy to think that you know better than most other developers, or to turn your nose up at X or Y bandwagon that your company's developers have jumped onto. But if you resist that close-mindedness and try to see the good side of even the technologies and techniques that you don't like, then you'll learn a lot more. That happened with me a few months ago, when I spent time learning more RSpec, and it finally clicked in my mind and I started to appreciate its strong points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find mentors.&lt;/strong&gt; If you're not getting much one-on-one guidance at work, I suggest (a) asking persistently until you at least have someone who can check in with your learning on a regular basis, and (b) looking for mentors outside of your company, such as at &lt;a href="https://firstrubyfriend.org"&gt;First Ruby Friend&lt;/a&gt;. The &lt;a href="https://www.reddit.com/r/rails"&gt;Rails subreddit&lt;/a&gt; is another good place to find a mentor, and I've found the &lt;a href="https://discord.com/invite/stimulus-reflex"&gt;StimulusReflex Discord server&lt;/a&gt; to be a welcoming place for my questions—and I haven't even learned StimulusReflex yet, but they have very active channels for Ruby, Rails, and lots more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Don't be a silo: connect more, communicate better
&lt;/h2&gt;

&lt;p&gt;Working remotely (as much as I love it) can make it harder to connect with others unless you're very intentional. I mean this both in the sense of getting to know my colleagues, and also in the sense of hashing out important information.&lt;/p&gt;

&lt;p&gt;An example of the latter is communicating with all stakeholders and making sure we're all on the same page. I've found myself in situations where different people want different things from a new feature that I'm working on. The first time this happened, it was fine because I just added extra features that weren't in the original spec, and everyone loved it. A few weeks later, as I worked on a different new feature, I again made adjustments mid-course based on feedback from a stakeholder, but this time it didn't turn out so well. The adjustments I made departed from the original spec in ways that I realized were unacceptable only &lt;em&gt;after&lt;/em&gt; a barrage of questions and complaints from other stakeholders. I then had to rush to redo some of my work on my last two days before going on vacation. Moral of the story: don't be the monkey in the middle! 🙈 Get everyone together in the same room (Zoom call) if necessary.&lt;/p&gt;

&lt;p&gt;As for getting to know my colleagues, I did great at the beginning when I scheduled an introductory Zoom call with each and every person in the product and engineering departments. However, it wasn't long before I lost touch with even the handful of juniors that started at the same time that I did, and only now am I reconnecting with them. My hope is that we can get back to telling each other about our struggles and successes, as well as sharing new things that we're learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I have a few other regrets about the past six months, but they all go back to these three lessons I've learned: find balance, keep learning, and don't be a silo.&lt;/p&gt;

&lt;p&gt;More fundamentally, I'm trying to shift my focus away from regrets and mistakes, and instead appreciate all that I've learned. These lessons are already helping me be happier and more fulfilled, and that makes me even more excited for all that I'll learn in the next six months.&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>RVTWS: a Ruby stack for modern web apps</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Fri, 24 Jun 2022 18:57:00 +0000</pubDate>
      <link>https://dev.to/fpsvogel/rvtws-a-ruby-stack-for-modern-web-apps-3eb2</link>
      <guid>https://dev.to/fpsvogel/rvtws-a-ruby-stack-for-modern-web-apps-3eb2</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Rails or Roda&lt;/li&gt;
&lt;li&gt;ViewComponent&lt;/li&gt;
&lt;li&gt;Turbo&lt;/li&gt;
&lt;li&gt;Web components&lt;/li&gt;
&lt;li&gt;StimulusReflex&lt;/li&gt;
&lt;li&gt;
Conclusion
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;When I started my career switch into software development two years ago, I decided to focus my efforts on Ruby. I did this for &lt;a href="https://fpsvogel.com/posts/2021/why-learn-ruby"&gt;a few reasons&lt;/a&gt;, but one of them is that Rails offers great "bang for the buck": there's a lot that I can build with just Rails, HTML, and CSS.&lt;/p&gt;

&lt;p&gt;However, this minimal vanilla stack becomes limiting when two factors come into play:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The MVC architecture of Rails won't always be enough to keep your code organized. You'll notice it painfully if your app ever grows beyond a small project.&lt;/li&gt;
&lt;li&gt;For your app to feel modern, the frontend will need to act like an SPA (Single-Page Application). The "official" way to do this is now &lt;a href="https://hotwired.dev"&gt;Hotwire&lt;/a&gt;, but there are other tools worth keeping in the toolbelt. More on that below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Making a new acronym for your favorite tech stack is a popular thing these days, so I'll coin a new acronym: &lt;strong&gt;RVTWS&lt;/strong&gt;… pronounced "erv toes"? Yes, this is great. It will go viral in no time.&lt;/p&gt;

&lt;p&gt;Joking aside, I'm using this acronym here only as an outline for this blog post, rather than for any marketing value. The "V" (ViewComponent) addresses point #1 above, and "TWS" are concerned with #2.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;R&lt;/strong&gt;ails or Roda&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V&lt;/strong&gt;iewComponent: &lt;em&gt;for frontend architecture&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T&lt;/strong&gt;urbo: &lt;em&gt;for an SPA feel, using the server&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;W&lt;/strong&gt;eb Components: &lt;em&gt;for an SPA feel, using the client&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt;timulusReflex: &lt;em&gt;for more complex frontend magic&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rails or Roda
&lt;/h2&gt;

&lt;p&gt;There's not much to say about &lt;a href="https://rubyonrails.org"&gt;Rails&lt;/a&gt;: it's boring tech, and therefore a good choice for most web apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://roda.jeremyevans.net"&gt;Roda&lt;/a&gt; is also worth considering. Unlike the batteries-included philosophy of Rails, Roda is bare-bones by default but highly extensible. This makes Roda apps fast and more architecturally flexible. Besides &lt;a href="http://roda.jeremyevans.net/documentation.html"&gt;the docs&lt;/a&gt;, here are some places to get started with Roda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://topenddevs.com/podcasts/ruby-rogues/episodes/building-with-just-what-you-need-using-roda-with-jeremy-evans-ruby-507"&gt;An interview&lt;/a&gt; with its creator Jeremy Evans.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jeremyevans/roda-sequel-stack"&gt;Roda + Sequel app skeleton&lt;/a&gt;, which has a similar purpose to the &lt;code&gt;rails new&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.fullstackruby.dev/fullstack-development/2022/06/03/what-would-it-take-for-roda-to-win"&gt;A post on Bridgetown's ongoing Roda integration&lt;/a&gt;, which is making Roda more accessible thanks to Bridgetown's batteries-included approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ViewComponent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials"&gt;Partials&lt;/a&gt; are the standard Rails way to define distinct parts of a view. Partials provide a way to separate out part of a template, but they don't provide a way to separate out view-related logic, which often ends up being thrown into models and/or controllers. This is one reason why models and controllers in Rails can so easily become huge and messy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://viewcomponent.org"&gt;ViewComponent&lt;/a&gt; provides a home for this view-related logic. &lt;a href="https://www.codewithjason.com/the-problem-that-viewcomponent-solves-for-me"&gt;This post on the Code with Jason blog&lt;/a&gt; explains it best. In short, ViewComponent fills a big gap in MVC architecture.&lt;/p&gt;

&lt;p&gt;Some readers may be wondering, &lt;em&gt;"Is that all? What about other architectural improvements, like service objects?"&lt;/em&gt; It's true that certain types of apps have problems best solved with certain design patterns, and maybe a very large app needs a specialized architecture. But in general, Rails models designed in a careful, object-oriented way can take you very far, and I think the popularity of service objects is unjustified because they can easily muddle up a codebase. Food for thought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.fullstackruby.dev/object-orientation/rails/2018/03/06/why-service-objects-are-an-anti-pattern"&gt;Why Service Objects are an Anti-Pattern&lt;/a&gt; at Fullstack Ruby&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.codewithjason.com/organize-rails-apps"&gt;How I Organize My Rails Apps&lt;/a&gt; at Code with Jason&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Turbo
&lt;/h2&gt;

&lt;p&gt;On to the frontend! &lt;a href="https://turbo.hotwired.dev"&gt;Turbo&lt;/a&gt; is part of Hotwire, which now ships with Rails. Turbo makes it really easy to give server-rendered pages a snappy SPA feel, where parts of the page are updated instantly instead of a full page reload.&lt;/p&gt;

&lt;p&gt;At the heart of Turbo is "HTML over the wire" (for which &lt;em&gt;HOTWire&lt;/em&gt; is an acronym), which means the server sending HTML fragments for partial page updates, which (here's the big win) eliminates the need for client-side state management. There are &lt;a href="https://htmldriven.dev/html-over-the-wire"&gt;lots&lt;/a&gt; of &lt;a href="https://github.com/guettli/frow--fragments-over-the-wire"&gt;tools&lt;/a&gt; taking this approach now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://unpoly.com"&gt;Unpoly&lt;/a&gt; and &lt;a href="https://htmx.org"&gt;HTMX&lt;/a&gt; are two of the most intriguing alternatives to Turbo because they are more framework-agnostic and have a flexible, concise syntax. Turbo, on the other hand, seems easier to get started with if you're in Rubyland.&lt;/p&gt;

&lt;p&gt;Sidenote: If you're wondering why all these "HTML over the wire" tools came about and what they're pushing back against, take a look at these two comparisons of web app architecture from 2005 vs. ten years later: one from Unpoly (&lt;a href="http://triskweline.de/unpoly-rugb/#/5"&gt;2005&lt;/a&gt;) vs. &lt;a href="http://triskweline.de/unpoly-rugb/#/11"&gt;2015&lt;/a&gt;), and another from the developer of Turbo's predecessor Turbolinks (&lt;a href="https://youtu.be/SWEts0rlezA?t=273"&gt;2005&lt;/a&gt; vs. &lt;a href="https://www.youtube.com/watch?v=SWEts0rlezA&amp;amp;t=397s"&gt;2016&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Besides Turbo, the other part of Hotwire is &lt;a href="https://stimulus.hotwired.dev"&gt;Stimulus&lt;/a&gt;, which typically is used for adding client-side reactivity in situations where you want to sprinkle in some JS. After all, you wouldn't want &lt;em&gt;every&lt;/em&gt; user interaction to involve a round trip to the server. So why am I including only Turbo here and not Stimulus?&lt;/p&gt;

&lt;h2&gt;
  
  
  Web components
&lt;/h2&gt;

&lt;p&gt;Actually, Stimulus is pretty cool because you can &lt;a href="https://stimulus-use.github.io/stimulus-use"&gt;compose multiple pre-built behaviors&lt;/a&gt; into one Stimulus controller, for a sort of functional approach to component behaviors. The tradeoff is that a growing web of Stimulus controllers (plus HTML data attributes associated with them) can become complex and hard to understand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components"&gt;Web components&lt;/a&gt; are an architecturally simpler way to add client-side behaviors, and they also have the advantage that they're a web standard. &lt;a href="https://www.fullstackruby.dev/fullstack-development/2022/01/04/how-ruby-web-components-work-together"&gt;This blog post on Fullstack Ruby&lt;/a&gt; shows the power of web components in the context of Ruby.&lt;/p&gt;

&lt;p&gt;Also, as illustrated in that post, you can use &lt;a href="https://www.ruby2js.com"&gt;Ruby2JS&lt;/a&gt; to write web components in Ruby. (You can likewise &lt;a href="https://www.ruby2js.com/examples/stimulus"&gt;write Stimulus controllers in Ruby&lt;/a&gt;.) In other words, you get the best of both worlds: the power of JavaScript on the frontend, and all the conveniences of Ruby syntax 🤩&lt;/p&gt;

&lt;h2&gt;
  
  
  StimulusReflex
&lt;/h2&gt;

&lt;p&gt;Turbo + web components can take you a long way in making your Rails app feel modern, but there are other tools in this space that can complement them. I've already mentioned Stimulus, but there's also &lt;a href="https://docs.stimulusreflex.com"&gt;StimulusReflex&lt;/a&gt; which is like Stimulus but on the server, and &lt;a href="https://cableready.stimulusreflex.com/"&gt;CableReady&lt;/a&gt; which is somewhat like &lt;a href="https://turbo.hotwired.dev/handbook/streams"&gt;Turbo Streams&lt;/a&gt; but more flexible. Be sure to give these a try if your app is highly interactive, or if you just want to expand your horizons beyond Hotwire.&lt;/p&gt;

&lt;p&gt;Also, &lt;a href="https://discord.com/invite/stimulus-reflex"&gt;the StimulusReflex Discord server&lt;/a&gt; is an amazing place even for discussing all the other tech mentioned in this post. (Which is very ironic. Ever since Hotwire's release, it has marketed itself as THE solution, not even mentioning the similar tools had already been coming out of the StimulusReflex community for two years. Even so, it's in this very community that you can find the best Hotwire support.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I've been thinking about this ideal Ruby stack because for the first time I'm working on a Rails app that has a React frontend, and it's been a painful adjustment. Sometimes it feels like I'm writing logic twice, once on the backend and again on the frontend. And what do I get for it? Smoother page transitions and buttons that do things without refreshing the page. That's nice, but do these simple enhancements have to involve so much extra work?&lt;/p&gt;

&lt;p&gt;On the other extreme, the "vanilla Rails" approach to writing views &lt;a href="https://www.fullstackruby.dev/fullstack-development/2022/06/03/what-would-it-take-for-roda-to-win#y-u-no-like-rails"&gt;is clunky and outdated&lt;/a&gt;, and Hotwire doesn't fix all the omissions. It's no wonder that people go looking for outside frontend solutions such as React.&lt;/p&gt;

&lt;p&gt;So I started dreaming about what frontend tools could give a smooth user experience &lt;em&gt;without&lt;/em&gt; so much extra complexity, and this "RVTWS" stack is the result. (Yeah, I need to work on that acronym.)&lt;/p&gt;

&lt;p&gt;On a final note, if you'd like to learn more about Hotwire and StimulusReflex, check out the resources that I've compiled for both of them &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs#rails-hotwire"&gt;in my "Learning Ruby" list&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>hotwire</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Dot syntax and dig! for Ruby hashes, benchmarked</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Mon, 09 May 2022 14:39:14 +0000</pubDate>
      <link>https://dev.to/fpsvogel/dot-syntax-and-dig-for-ruby-hashes-benchmarked-4kgp</link>
      <guid>https://dev.to/fpsvogel/dot-syntax-and-dig-for-ruby-hashes-benchmarked-4kgp</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Baselines&lt;/li&gt;
&lt;li&gt;Dot syntax&lt;/li&gt;
&lt;li&gt;Dig with errors&lt;/li&gt;
&lt;li&gt;Moral of the story&lt;/li&gt;
&lt;li&gt;Appendix A: regular &lt;code&gt;dig&lt;/code&gt; on a hash with error-raising defaults&lt;/li&gt;
&lt;li&gt;
Appendix B: the benchmark code
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;Recently I heard about this convenient feature of &lt;a href="https://hexdocs.pm/elixir/Map.html"&gt;Elixir maps&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To access atom keys, one may also use the &lt;code&gt;map.key&lt;/code&gt; notation. Note that &lt;code&gt;map.key&lt;/code&gt; will raise a &lt;code&gt;KeyError&lt;/code&gt; if the &lt;code&gt;map&lt;/code&gt; doesn't contain the key &lt;code&gt;:key&lt;/code&gt;, compared to &lt;code&gt;map[:key]&lt;/code&gt;, that would return &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nice! This is something I've been wishing for in Ruby. In a current project I have a configuration hash that is passed around and used in a variety of objects. The hash is quite large and several levels deep, so my code abounds with chains of &lt;code&gt;Hash#fetch&lt;/code&gt; such as &lt;code&gt;config.fetch(:item).fetch(:template).fetch(:variants)&lt;/code&gt;. Which, as you can imagine, makes some lines very long and not particularly readable 😒&lt;/p&gt;

&lt;p&gt;Two notes on why I've been doing it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The reason I use &lt;code&gt;fetch&lt;/code&gt; instead of &lt;code&gt;config[:item][:template][:variants]&lt;/code&gt; or &lt;code&gt;config.dig(:item, :template, :variants)&lt;/code&gt; is that the &lt;code&gt;KeyError&lt;/code&gt; raised by &lt;code&gt;fetch&lt;/code&gt;, in case of a missing key, is more helpful than the default &lt;code&gt;nil&lt;/code&gt; value from brackets or &lt;code&gt;dig&lt;/code&gt;. In fact, that &lt;code&gt;nil&lt;/code&gt; could cause a major debugging headache if it results in an error somewhere else far from where the &lt;code&gt;nil&lt;/code&gt; originated.&lt;/li&gt;
&lt;li&gt;If you're wondering why I'm using a raw hash instead of a custom &lt;code&gt;Config&lt;/code&gt; class with syntactic sugar such as &lt;code&gt;config[:item, :template, :variants]&lt;/code&gt;: that could be a great idea in some projects! But in this project, some objects use only a part of the config and I don't want to pass the entire config into those objects. Also, some objects perform hash operations using parts of the config. So if I'm creating separate &lt;code&gt;Config&lt;/code&gt; objects just to wrap inner hashes from the main &lt;code&gt;Config&lt;/code&gt;, and if I'm converting these &lt;code&gt;Config&lt;/code&gt; objects into a hash at various points, then it seems I should simply use a hash to begin with. In this project it's simpler to deal with hashes all the time, so that I don't have to ask myself, &lt;em&gt;"Let's see, is this a &lt;code&gt;Config&lt;/code&gt; object here, or has it turned into a hash?"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if we stick with a raw hash, can we hack our way to a more concise alternative to those repeated &lt;code&gt;fetch&lt;/code&gt;es, but with the same safety net of a &lt;code&gt;KeyError&lt;/code&gt;? Of course! This is Ruby, after all, where anything is possible. But whether it's &lt;em&gt;advisable&lt;/em&gt;… that's the real question. In this post, we'll look at two possible syntaxes along with their performance and usability implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dot syntax: &lt;code&gt;config.item.template.variants&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dig with errors: &lt;code&gt;config.dig!(:item, :template, :variants)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Originally I set out to find a performant approach to dot syntax, but by the end I had changed my mind, for reasons that I'll explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Baselines
&lt;/h2&gt;

&lt;p&gt;First, here are benchmarks on standard syntax (mostly). For the benchmark code, see the end of this post.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Bracket notation: &lt;code&gt;hash[:a][:b]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dig: &lt;code&gt;hash.dig(:a, :b)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chained &lt;code&gt;fetch&lt;/code&gt;: &lt;code&gt;hash.fetch(:a).fetch(:b)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A shorter &lt;code&gt;fetch&lt;/code&gt; alias: &lt;code&gt;hash &amp;gt; :a &amp;gt; :b&lt;/code&gt;. Because why not.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                           user     system      total        real
1. brackets           :  0.003332   0.000000   0.003332 (  0.003332)
2. dig                :  0.002877   0.000823   0.003700 (  0.003704)
3. fetch              :  0.005040   0.000000   0.005040 (  0.005044)
4. fetch alias        :  0.005012   0.000000   0.005012 (  0.005012)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These are all very performant. But remember, brackets and &lt;code&gt;dig&lt;/code&gt; return &lt;code&gt;nil&lt;/code&gt; where I want a &lt;code&gt;KeyError&lt;/code&gt;, and chained &lt;code&gt;fetch&lt;/code&gt; is what I'm trying to get away from.&lt;/li&gt;
&lt;li&gt;In some runs, &lt;code&gt;dig&lt;/code&gt; (#2) was faster than brackets (#1), but more often brackets win by a hair.&lt;/li&gt;
&lt;li&gt;Chained &lt;code&gt;fetch&lt;/code&gt; (#3) is consistently slower than brackets here in the benchmarks, but my project's test suite does not run any faster when I replace all calls to &lt;code&gt;fetch&lt;/code&gt; with brackets. It's a good reminder that benchmarks don't always reflect real-life performance.&lt;/li&gt;
&lt;li&gt;Even though the &lt;code&gt;fetch&lt;/code&gt; alias (#4) is just as fast as &lt;code&gt;fetch&lt;/code&gt; itself in the benchmarks, my project's test suite took 20% longer to run when I replaced all calls to &lt;code&gt;fetch&lt;/code&gt; with an alias. 20% slower is not much, especially since all of my tests run in well under one second. But there's also the fact that while &lt;code&gt;config &amp;gt; … &amp;gt; …&lt;/code&gt; looks really cool, it is a bit cryptic (likely to confuse my future forgetful self), and I have to surround it with parentheses every time I want to call a method on the return value. Still, I was curious about the performance hit and that's why I included the &lt;code&gt;fetch&lt;/code&gt; alias here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dot syntax
&lt;/h2&gt;

&lt;p&gt;Here are a few approaches to dot notation for hashes or hash-like structures, benchmarked. Keep in mind that I measured only access (reading) performance, not initialization or writing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Faux dot notation by flattening a hash and giving it composite keys, as in &lt;code&gt;config[:"item.template.variants"]&lt;/code&gt;. I copied this approach &lt;a href="https://snippets.aktagon.com/snippets/738-dot-notation-for-ruby-configuration-hash"&gt;from here&lt;/a&gt;, with the main difference that I use symbols as keys because they're more performant than strings. Note that &lt;code&gt;:"string"&lt;/code&gt; is similar to &lt;code&gt;"string".to_sym&lt;/code&gt; but faster because a string is not created every time. Also, this approach uses brackets, but only because that hash's bracket accessor (&lt;code&gt;Hash#[]&lt;/code&gt;) is overridden to use &lt;code&gt;fetch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An OpenStruct, which is sometimes suggested in these sorts of conversations.&lt;/li&gt;
&lt;li&gt;Augmenting a single hash with methods corresponding to its keys.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html"&gt;ActiveSupport::OrderedOptions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/adsteel/hash_dot"&gt;hash_dot&lt;/a&gt; gem. Also, my benchmark code is based on &lt;a href="https://github.com/adsteel/hash_dot#benchmarks"&gt;the benchmarks in hash_dot's README&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hashie/hashie#methodaccess"&gt;Hashie&lt;/a&gt; gem.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                           user     system      total        real
1. flat composite keys:  0.003461   0.000000   0.003461 (  0.003461)
2. OpenStruct         :  0.009731   0.000000   0.009731 (  0.009772)
3. per-hash dot access:  0.015300   0.000000   0.015300 (  0.015304)
4. AS::OrderedOptions :  0.070637   0.000000   0.070637 (  0.070640)
5. hash_dot           :  0.163008   0.000000   0.163008 (  0.163076)
6. hashie             :  0.163450   0.000000   0.163450 (  0.163451)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some approaches to dot notation involve more annoying setup than others, and/or significant limitations. For example, the flattened hash with composite keys (#1) is super fast, but it's far from the vanilla nested hash that I began with. This makes certain hash operations more complicated, such as iterating over hash keys. For my purposes it's not worth the headache.&lt;/li&gt;
&lt;li&gt;The OpenStruct is faster than I thought it would be. But its fatal flaws, for my purposes, are that it's not a hash and therefore lacks a lot of functionality, and also it doesn't raise an error for a nonexistent attribute (like the &lt;code&gt;KeyError&lt;/code&gt; from &lt;code&gt;fetch&lt;/code&gt;) but instead returns &lt;code&gt;nil&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Per-hash dot access (#3) is the fastest true dot notation for a hash. (Note that it only works for a hash that doesn't get any new keys once it's set up, which is just fine for my config hash.) However, when applied in my project, it still made my tests run for 70% longer. Again, that's not as bad as it sounds for my sub-1-second test suite.&lt;/li&gt;
&lt;li&gt;But then something unexpected happened as soon as I replaced my project's calls to &lt;code&gt;fetch&lt;/code&gt; with dot notation. My code looked &lt;em&gt;more messy&lt;/em&gt; even though it was now &lt;em&gt;more concise&lt;/em&gt;. The reason, I think, is that there was no longer a slew of (syntax-highlighted) symbols at the points where I access the config hash, and so it was a bit harder to see at a glance where config values were being used. Instead of brightly-colored symbols evenly spaced by &lt;code&gt;fetch&lt;/code&gt;, my eyes now saw only a mush of method calls until my brain processed the words and told me whether that's a place where the config hash is accessed. Hmm. Now I was wondering if there was a way to keep the symbols involved, but in a more concise way than chaining &lt;code&gt;fetch&lt;/code&gt; 🤔&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dig with errors
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Hash#dig&lt;/code&gt; looks nice: &lt;code&gt;hash.dig(:item, :template, :variants)&lt;/code&gt;. But again, the problem is that it defaults to &lt;code&gt;nil&lt;/code&gt; for nonexistent keys. What if we could make a similar method that raises a &lt;code&gt;KeyError&lt;/code&gt; instead?&lt;/p&gt;

&lt;p&gt;This has actually been proposed as an addition to Ruby several times (&lt;a href="https://bugs.ruby-lang.org/issues/15563"&gt;1&lt;/a&gt;, &lt;a href="https://bugs.ruby-lang.org/issues/14602"&gt;2&lt;/a&gt;, &lt;a href="https://bugs.ruby-lang.org/issues/12282"&gt;3&lt;/a&gt;) with various names including &lt;code&gt;dig!&lt;/code&gt;,  &lt;code&gt;deep_fetch&lt;/code&gt;, and &lt;code&gt;dig_fetch&lt;/code&gt;. But the method seems unlikely to be added in the near future. So… let's do it ourselves!&lt;/p&gt;

&lt;p&gt;Here are a few different implementations, with benchmarks. There are also a couple of gems for it, &lt;a href="https://github.com/dogweather/digbang"&gt;dig_bang&lt;/a&gt; and &lt;a href="https://github.com/pewniak747/deep_fetch"&gt;deep_fetch&lt;/a&gt;, but I didn't include them here because &lt;code&gt;dig_bang&lt;/code&gt; uses &lt;code&gt;reduce&lt;/code&gt; (#4 below) and &lt;code&gt;deep_fetch&lt;/code&gt; uses recursion, which performs the same.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Regular &lt;code&gt;dig&lt;/code&gt; on a hash that has had its defaults set such that it raises an error for nonexistent keys.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Hash#case_dig!&lt;/code&gt;, which raises a &lt;code&gt;KeyError&lt;/code&gt; for nonexistent keys. It's called &lt;code&gt;case_dig!&lt;/code&gt; because it's implemented with a simple case statement.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Hash#while_dig!&lt;/code&gt;, similar but implemented with a &lt;code&gt;while&lt;/code&gt; loop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Hash#reduce_dig!&lt;/code&gt;. This is the "most Ruby" implementation.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                           user     system      total        real
1. dig, error defaults:  0.003750   0.000000   0.003750 (  0.003750)
2. case_dig!          :  0.007850   0.000000   0.007850 (  0.007856)
3. while_dig!         :  0.014849   0.000000   0.014849 (  0.014852)
4. reduce_dig!        :  0.027889   0.000056   0.027945 (  0.027950)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most vanilla approach is to use regular &lt;code&gt;dig&lt;/code&gt; on a hash that's been given &lt;code&gt;KeyError&lt;/code&gt;-raising defaults. This is a bad idea in a way that's hard to explain in a bullet point. See Appendix A if you want to know.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reduce_dig!&lt;/code&gt; (#4) is the most idiomatic and flexible implementation, so it's probably what you should use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;while_dig!&lt;/code&gt; (#3) is for you if you want to &lt;del&gt;sell your soul&lt;/del&gt; trade idiomatic Ruby for a bit of extra speed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;case_dig!&lt;/code&gt; (#2) throws aesthetics and flexibility completely out the window because it's implemented with a case statement, and it can only dig as deep as the case statement is tall. But in my project I don't foresee ever needing to dig more than four levels into a hash, so for me it's perfect 🌟 Best of all, my tests don't run any slower now than they used to. But please don't copy me. That case statement is truly ugly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Moral of the story
&lt;/h2&gt;

&lt;p&gt;In the middle of all this, I seriously considered giving up and just going back to &lt;code&gt;fetch&lt;/code&gt;, because it's the most performant and any other syntax risks making my code more cryptic to my future self. When I see &lt;code&gt;config.fetch(:item)&lt;/code&gt; I know I'm dealing with a hash, unlike when I see &lt;code&gt;config.item&lt;/code&gt;. I'm sure even &lt;code&gt;config.dig!(:item, :template)&lt;/code&gt; will give my future self pause. For me this cost is outweighed by the better readability that I get in return, but it's surprising that this (and not performance) is what made the decision a difficult one.&lt;/p&gt;

&lt;p&gt;Which leads into the other surprising takeaway: in this case it wasn't hard to custom-build a very performant solution for my project. So maybe I should try a DIY mindset more often, rather than immediately reaching for a gem (or ten).&lt;/p&gt;

&lt;p&gt;In the end, maybe the real cost of my solution was in the absurd amount of time that I spent on all this benchmarking, hairsplitting, yak shaving, and bikeshedding. Enough! But I hope you've enjoyed my little adventure as much as I'm enjoying seeing it finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix A: regular &lt;code&gt;dig&lt;/code&gt; on a hash with error-raising defaults
&lt;/h2&gt;

&lt;p&gt;So why is this a bad idea? It comes with no performance penalty because it's just regular &lt;code&gt;dig&lt;/code&gt; on a regular hash. What could go wrong?&lt;/p&gt;

&lt;p&gt;The problem is that I have to modify my config hash in the beginning to give it defaults that raise a &lt;code&gt;KeyError&lt;/code&gt;. Recall that I also had to modify the hash when I tried per-hash dot access (#3 in the benchmarks on dot syntax above). But this time I'm less comfortable with the modification, because this one can "slip out" in less noticeable ways.&lt;/p&gt;

&lt;p&gt;For example, if at some point in my code the config hash is operated on in a way that creates a derived hash (e.g. by calling &lt;code&gt;map&lt;/code&gt; on it and using the result), that derived hash would be a fresh new hash without the &lt;code&gt;KeyError&lt;/code&gt; defaults.&lt;/p&gt;

&lt;p&gt;That new hash might get passed around, with me thinking it's the original that has the special defaults. I might use &lt;code&gt;dig&lt;/code&gt; on the hash, and &lt;code&gt;dig&lt;/code&gt; would work as in any hash (without my trusty &lt;code&gt;KeyError&lt;/code&gt;) without me ever knowing that anything was missing 💀&lt;/p&gt;

&lt;p&gt;So this approach is too fragile for my liking. Plus, my future self might wonder &lt;em&gt;"Why did I use &lt;code&gt;dig&lt;/code&gt; and not fetch?"&lt;/em&gt; until future self re-discovers my hack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix B: the benchmark code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'ostruct'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_support/ordered_options'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'hash_dot'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'hashie'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_support/core_ext/object/blank'&lt;/span&gt;

&lt;span class="c1"&gt;#### SETUP&lt;/span&gt;

&lt;span class="c1"&gt;## FOR BASELINE BENCHMARKS&lt;/span&gt;

&lt;span class="c1"&gt;# regular hash&lt;/span&gt;
&lt;span class="n"&gt;vanilla&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;category: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s2"&gt;"Urban"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# fetch alias&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;
  &lt;span class="kp"&gt;alias_method&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:fetch&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;## FOR DOT BENCHMARKS&lt;/span&gt;

&lt;span class="c1"&gt;# a flattened hash with composite keys&lt;/span&gt;
&lt;span class="c1"&gt;# from https://snippets.aktagon.com/snippets/738-dot-notation-for-ruby-configuration-hash&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_namespace_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;
        &lt;span class="n"&gt;to_namespace_hash&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;to_namespace_hash&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:merge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;category: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s2"&gt;"Urban"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_namespace_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;flat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;KeyError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="n"&gt;possible_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt; &lt;span class="sr"&gt;/.*?&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;.*?/i&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;delete_if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:blank?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Key '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' not found. Did you mean one of:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;possible_keys&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

&lt;span class="c1"&gt;# OpenStruct&lt;/span&gt;
&lt;span class="n"&gt;ostruct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;category: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s2"&gt;"Urban"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# per-hash dot access&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allow_dot_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;allow_dot_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# ActiveSupport::OrderedOptions&lt;/span&gt;
&lt;span class="n"&gt;asoo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderedOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;asoo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderedOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;asoo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OrderedOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;asoo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Urban"&lt;/span&gt;

&lt;span class="c1"&gt;# hash_dot gem&lt;/span&gt;
&lt;span class="n"&gt;hash_dot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dot&lt;/span&gt;

&lt;span class="n"&gt;my_dot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allow_dot_access&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;category: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s2"&gt;"Urban"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

&lt;span class="c1"&gt;## FOR DIG! BENCHMARKS&lt;/span&gt;

&lt;span class="c1"&gt;# with error defaults&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_key_error_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"key not found: :&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;vanilla_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;
      &lt;span class="n"&gt;add_key_error_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;vanilla_hash&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;errorful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;add_key_error_defaults&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="ss"&gt;address: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;category: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;desc: &lt;/span&gt;&lt;span class="s2"&gt;"Urban"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# dig! implementations&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;
  &lt;span class="c1"&gt;# ewwwwwwwwwwwww&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;case_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key4&lt;/span&gt;
      &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;key3&lt;/span&gt;
      &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;key2&lt;/span&gt;
      &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;while_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;
      &lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="nb"&gt;hash&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reduce_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#### BENCHMARKS&lt;/span&gt;

&lt;span class="n"&gt;iterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"BASELINES:"&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1. brackets           :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2. dig                :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"3. fetch              :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"4. fetch alias        :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:address&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"DOT:"&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1. flat composite keys:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"address.category.desc"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2. OpenStruct         :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;ostruct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"3. per-hash dot access:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;my_dot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"4. AS::OrderedOptions :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;asoo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"5. hash_dot           :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;hash_dot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Hashie&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Extensions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MethodAccess&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"6. hashie             :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"DIG!:"&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1. dig, error defaults:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;errorful&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2. case_dig!          :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;case_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"3. while_dig!         :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;while_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;bm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"4. reduce_dig!        :"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;vanilla&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce_dig!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ruby</category>
    </item>
    <item>
      <title>How to find your first Rails job</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Tue, 01 Feb 2022 14:18:53 +0000</pubDate>
      <link>https://dev.to/fpsvogel/how-to-find-your-first-rails-job-1ajf</link>
      <guid>https://dev.to/fpsvogel/how-to-find-your-first-rails-job-1ajf</guid>
      <description>&lt;p&gt;I've just landed my first developer job, a U.S.-based (but remote) junior position in fullstack Ruby on Rails 🎉 Here are some reflections on the job hunt, along with tips on finding your first Rails job.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Why Ruby?&lt;/li&gt;
&lt;li&gt;Where to look for Rails job postings&lt;/li&gt;
&lt;li&gt;My job search: a bird's-eye view&lt;/li&gt;
&lt;li&gt;Resume strategies&lt;/li&gt;
&lt;li&gt;What to ask in the initial interview&lt;/li&gt;
&lt;li&gt;Technical exercises&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;
Appendix: Why a junior role?
&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Ruby?
&lt;/h2&gt;

&lt;p&gt;There's a good chance that you pity me for going through the harrowing experience of looking for a junior Rails job. Why did I choose Ruby? Why not a JS stack where junior roles are more common?&lt;/p&gt;

&lt;p&gt;For me Ruby was worth the risk of a longer job search because (a) I enjoy it a lot and (b) Rails is great for building up a portfolio quickly. If you're still skeptical, &lt;a href="https://fpsvogel.com/posts/2021/why-learn-ruby"&gt;here's my post expanding on these two points&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, my job search ended up taking only two months. But before that I spent a year and a half studying and practicing part-time, while working full-time in customer support to pay the bills. For details and recommended learning resources, see &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs"&gt;my ongoing study guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also looked for a junior role specifically. If you're wondering why, see Appendix: Why a junior role? below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to look for Rails job postings
&lt;/h2&gt;

&lt;p&gt;I found most of my job leads in the &lt;a href="https://www.rubyonrails.link/"&gt;Ruby on Rails Link&lt;/a&gt; community on Slack, and on &lt;a href="https://railsdevs.com/"&gt;Rails Devs&lt;/a&gt;. I also found a few on Twitter and on the &lt;a href="https://discord.com/invite/stimulus-reflex"&gt;StimulusReflex&lt;/a&gt; community on Discord.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What about LinkedIn?&lt;/em&gt; Yes, be sure to have a LinkedIn profile, if only for recruiters to be able to contact you. But I found only a few junior roles on LinkedIn, and none that I applied for. Some other sites that might be worth checking just in case: &lt;a href="https://www.indeed.com/"&gt;Indeed&lt;/a&gt;, &lt;a href="https://angel.co/"&gt;AngelList&lt;/a&gt;, &lt;a href="https://hired.com"&gt;Hired&lt;/a&gt;, &lt;a href="https://www.railsgigs.com/"&gt;RailsGigs&lt;/a&gt;, and the &lt;a href="https://jobs.gorails.com/"&gt;GoRails job board&lt;/a&gt;. If you live outside the U.S., be sure to look in local job boards if there are any in your area. (If you're not sure, try asking in &lt;a href="https://www.rubyonrails.link/"&gt;Ruby on Rails Link&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  My job search: a bird's-eye view
&lt;/h2&gt;

&lt;p&gt;Over two months, I applied to seven companies. Most were startups, and only one of them (thoughtbot) is widely known in the Ruby community. I got an interview at six out of the seven companies. In five of them I moved past the first interview.&lt;/p&gt;

&lt;p&gt;(In the one where I didn't move past the first interview, it was because I asked about the salary range and it was too low—or rather, the interviewer did the classic "Well, what do YOU want to be paid?" and my answer was evidently far beyond what they thought reasonable.)&lt;/p&gt;

&lt;p&gt;Speaking of salaries, there's a huge range in what people think a junior's salary should be. Among the full-time U.S. junior job postings I came across, not counting internships, the range in advertised salaries was $40k/year to $120k/year.&lt;/p&gt;

&lt;p&gt;The application process varied widely between the companies, from the simplest with just two interviews to the most complex with five interviews plus a take-home project. All of them had some sort of technical exercise, whether as part of an interview or as a take-home project.&lt;/p&gt;

&lt;p&gt;I'll describe the technical exercises in more detail, but first let's back up to the resume and the initial interview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resume strategies
&lt;/h2&gt;

&lt;p&gt;To get an initial interview, having an impressive resume is key. To give you some ideas on how to polish your resume, here are things that interviewers said they liked about my resume. By the way, it's always worth asking in an interview, "What did you like about my resume? And how could it be better?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lots of projects.&lt;/strong&gt; In one of my early interviews, I got a helpful answer to the "how could it be better" question: "More projects." So I built &lt;a href="https://fpsvogel.com/posts/2022/doctor-lookup-health-provider-search-tool"&gt;a series of small apps&lt;/a&gt; over the following weeks. These helped fill out my resume, and they were a great learning experience. I've also heard good arguments (though not from interviewers) on the other side: instead of building lots of small projects, buckle down and build one big and impressive project. That's actually the path I started on when I first learned Rails, and I do plan on returning to my "serious" hobby app, but in these early stages of my Rails journey I learned more when I was building lots of small projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a blog.&lt;/strong&gt; One interviewer found me through &lt;a href="https://rubyrogues.com/bridgetown-rb-ft-felipe-vogel-ruby-526"&gt;a podcast episode where I was featured&lt;/a&gt;. I was invited to the podcast because of &lt;a href="https://fpsvogel.com/posts/2021/build-a-blog-with-bridgetown"&gt;a blog post&lt;/a&gt; that I wrote (reposted &lt;a href="https://dev.to/fpsvogel/build-a-static-site-in-ruby-with-bridgetown-45kg"&gt;on DEV&lt;/a&gt;so that more people would see it). Moral of the story: write a blog! But even if your blog doesn't lead to any special opportunities, it's still worthwhile. Not only will your communication skills get a boost, but you can even learn something more thoroughly just by writing about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a study guide.&lt;/strong&gt; I got compliments on &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs"&gt;my study guide&lt;/a&gt; in two interviews, but it's another one of those things that's useful to do even if no one notices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are efforts that I didn't get comments on, but I'm sure they didn't hurt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Polish your GitHub portfolio.&lt;/strong&gt; Here's mine: &lt;a href="https://github.com/fpsvogel"&gt;github.com/fpsvogel&lt;/a&gt;. Make sure each of your pinned projects has a nice README including a summary of why it's on portfolio. If you want to spend even more time perfecting your READMEs, &lt;a href="https://github.com/matiassingers/awesome-readme"&gt;here's an awesome README list&lt;/a&gt; to give you ideas, and here are a few that are built specifically as part of a learner's portfolio of Rails apps: &lt;a href="https://github.com/lortza/tarot"&gt;lortza/tarot&lt;/a&gt;, &lt;a href="https://github.com/lortza/sorrygirl"&gt;lortza/sorrygirl&lt;/a&gt;, &lt;a href="https://github.com/lortza/therapy_tracker"&gt;lortza/therapy_tracker&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make a web version of your resume.&lt;/strong&gt; Here's mine: &lt;a href="https://fpsvogel.com/about/"&gt;fpsvogel.com/about&lt;/a&gt;. This is in addition to (not instead of) the PDF version that I submitted in applications. &lt;a href="https://www.overleaf.com/read/mgxfqgqngjqf"&gt;Here's my resume in PDF&lt;/a&gt; as of January 2022. I made it with LaTeX, but a plain old word processor would work too. Remember, keep it under one page!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to ask in the initial interview
&lt;/h2&gt;

&lt;p&gt;Once your resume is polished up, you're more likely to be invited to an interview. We tend to think most about giving good answers in an interview, but asking good questions is just as important—after all, you are interviewing the company as much as they are interviewing you. Your goal, besides making a good impression, is to find out whether that company would be a good fit for you. Here are the questions I asked in my initial interviews, along with my motivation for asking some of them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"What extra onboarding and support would I have in the junior role?"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;This helped me gauge whether it's truly a junior role where I'd get better learning opportunities, or just a regular role with lower pay and lower expectations.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What kind of code reviews do you all do?"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Even outside the junior role, developers should be helping each other learn, and code reviews are one way that's done.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How is your testing suite these days? What is your test coverage?"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I wanted to be sure to work at a company that follows best practices, especially automated testing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How often do your developers have to work odd hours or take care of unexpected issues?"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Some companies require their developers to be on call during off hours. And in some companies, developers frequently have to put out fires in production. I wanted to avoid these as much as possible.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What is the salary range for the position?"&lt;/strong&gt; (if a salary range was not advertised)

&lt;ul&gt;
&lt;li&gt;If they straightforwardly tell you the salary range, it's a good idea to double check it in a follow-up email, just so you have it in writing.&lt;/li&gt;
&lt;li&gt;If they don't give a salary range and instead ask what salary you're looking for, that's a mark against the company in my book, because it makes me feel that they were too lazy to research salaries and they want to pay me as little as they can get away with. Try to resist the pressure to give a low number, and instead name the optimistic/best-case salary that you're aiming for, and add on "but I'm willing to negotiate."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Is this a W-2 or contract position?"&lt;/strong&gt; (if it's not clear already)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What kind of health insurance do you offer?"&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I live in the U.S., with its *ahem* &lt;em&gt;unique&lt;/em&gt; approach to healthcare, so I wanted to make sure that I'd get good health insurance through work.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;If you're not sure what the company's product does: &lt;strong&gt;"I looked through your website and I see that you all do X, but I'm still not clear on Y. Could you fill me in on the details?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If you're not sure &lt;em&gt;why&lt;/em&gt; the company exists, or what gap they're filling: &lt;strong&gt;"I'm curious to hear how your company started. Could you give me a quick rundown of that?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If the interviewer is a developer (though this is more likely to be the case in a second or third interview): &lt;strong&gt;"Where did you work before this company, and why did you move? How has this company been better, and what are some areas where the company could improve?"&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are &lt;a href="https://www.google.com/search?q=questions+to+ask+in+a+developer+job+interview"&gt;lots of other questions&lt;/a&gt; you could ask in the initial interview. Just be sure to ask about anything that will give you a better feel for whether the company will be a good fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical exercises
&lt;/h2&gt;

&lt;p&gt;Of the five technical exercises that I did, three were take-home, and two were live coding exercises, where an interviewer watched as I wrote code and as I explained what I was doing and why. For the take-home exercises, I explained my thinking in a follow-up interview after I'd finished an exercise.&lt;/p&gt;

&lt;p&gt;Side note: If I could have a do-over of my job search, I would be more persistent about finding out the salary &lt;em&gt;before&lt;/em&gt; doing a technical exercise, so as not to do so many of them. Remember what I said earlier about asking for the salary range in the first interview? I was a bit lax on that until the last weeks of my job search. At least now you get to learn more about technical exercises…&lt;/p&gt;

&lt;p&gt;Here are the five technical exercises that I did, one for each company.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A take-home exercise where I built a Rails app that provides a user-friendly search interface to an API, and shows the search results. No time limit was given.&lt;/li&gt;
&lt;li&gt;A take-home exercise where I built a Rails API based on a JSON dataset. I was asked to spend one hour on it. Bonus points were given if I wrote automated tests.&lt;/li&gt;
&lt;li&gt;A timed (two-hour) HackerRank take-home exercise where I wrote a Ruby script that gets data from an API, processes it, and outputs the results to a file according to specifications given in the instructions. Automated tests were also provided, so that my goal was to make the tests pass.&lt;/li&gt;
&lt;li&gt;A one-hour live coding exercise where I got partway through solving the &lt;a href="https://kata-log.rocks/gilded-rose-kata"&gt;Gilded Rose Kata&lt;/a&gt; in Ruby. In this version of the kata, automated tests were provided for me in order to save time. Again, my goal was to make the tests pass.&lt;/li&gt;
&lt;li&gt;A half-hour live coding exercise where I wrote a command-line hangman game in Ruby.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are a few skills that I could tell the interviewers were looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code readability,&lt;/strong&gt; which involves naming things well, using object-oriented design, and avoiding &lt;a href="https://refactoring.guru/refactoring/smells"&gt;code smells&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.codewithjason.com/program-feedback-loops/"&gt;short feedback loops&lt;/a&gt;&lt;/strong&gt; were important in both live coding exercises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated testing.&lt;/strong&gt; After the first two take-home exercises listed above, interviewers appreciated that I wrote tests even though it wasn't required, and in two of the other three exercises I had to interact with pre-written tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting a lot done.&lt;/strong&gt; This is a catch-all for being fluent enough in Ruby and Rails to build something in a short amount of time. For take-home exercises where no time limit is given, the reality is that "getting a lot done" means spending a lot of time on the exercise. If possible, choose a weekend or a less-busy-than-average few days where you can dedicate large blocks of time to it. If you have time, write on your blog or in the GitHub README about how you did the project. &lt;a href="https://fpsvogel.com/posts/2022/doctor-lookup-health-provider-search-tool"&gt;Here's my blog post&lt;/a&gt; about how I did the first take-home exercise listed above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're wondering how to build up these skills, take a look at &lt;a href="https://fpsvogel.com/posts/2021/review-ruby-and-rails-for-job-interview"&gt;the Ruby and Rails review reading list&lt;/a&gt; which I've recently been working through. &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs"&gt;My ongoing study guide&lt;/a&gt; has a fuller list of resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Going into the job search, I had grim expectations for what lay ahead. I hadn't heard good things about the junior job market in Ruby. But I was pleasantly surprised in a couple of ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I got an interview at every company that I applied to, except one.&lt;/li&gt;
&lt;li&gt;My job search lasted only two months.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the other hand, I see how my job search could easily have taken longer, since most junior positions don't pay as much as I was looking for.&lt;/p&gt;

&lt;p&gt;The most difficult parts of the job search were how much was expected on my resume, how intentionally I had to ask questions in interviews, and how nerve-racking the technical exercises were. If you're a junior Rails job-seeker yourself, I hope you'll find some use in my notes above on each of these areas. Good luck on your job search!&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: Why a junior role?
&lt;/h2&gt;

&lt;p&gt;Junior roles are a funny thing. Most people appreciate why they exist, but I've also sensed an undercurrent of contempt for them. I'm not saying most developers bash on junior roles. Nine times out of ten, I got helpful replies when I asked people for tips or leads about junior/entry-level jobs.&lt;/p&gt;

&lt;p&gt;What I mean is that it's common to admire developers who skipped being a junior and figured everything out "in the real world." Some people take this admiration so far that they discount junior roles entirely. I've been told that I'm "projecting a position of weakness" by saying that I'm looking for a junior role, and that I should instead build a product and start my own company so that maybe in a year &lt;em&gt;I'll&lt;/em&gt; be the boss hiring people.&lt;/p&gt;

&lt;p&gt;(￣(エ)￣)ゞ&lt;/p&gt;

&lt;p&gt;Let's set aside the facts that I don't want to start my own company, I do try to convey competence and not helplessness to potential employers, and I don't need &lt;em&gt;and am not looking for&lt;/em&gt; extra hand-holding on every little thing—I am self-taught, after all.&lt;/p&gt;

&lt;p&gt;Those facts aside, here's why I applied to junior roles almost exclusively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Because that's how I think I'll grow the most.&lt;/strong&gt; I was a teacher in my previous career, so I appreciate the power of learning by example. As a new developer, I'll learn most quickly and most thoroughly if I'm working with experienced developers who exemplify best practices. I could probably (painfully) learn all the same things on my own over time, but it would take much longer. So I don't believe I'm holding myself back by going for a junior role—quite the opposite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Because I didn't have time to apply to everything.&lt;/strong&gt; During the job hunt I was already working a full-time job in order to pay the bills. Also, I spent most of my free time filling in the gaps on my resume, which I felt was a better use of time than filling out dozens of long-shot applications for mid-level positions. Even the few applications that I did undergo were time-consuming in themselves, due to their sometimes long process of multiple interviews plus a take-home programming exercise. So I wanted to maximize my chances of getting a job by applying to my top (and most realistic) choices while continually improving my resume. This approach was also a lot more enjoyable than making job applications my new default evening activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But one time I did apply for a mid-level position with not-too-daunting qualifications. It didn't turn out well. I passed the recruiter interview and the technical exercise, only to be shut down a few minutes into an interview with the CEO and lead developer, when they realized I was looking for my first programming job. I'm sure this only happened because of poor preparation on their part, but still I couldn't help being discouraged from spending more of my limited free time on mid-level applications.&lt;/p&gt;

&lt;p&gt;Also, I didn't apply for just &lt;em&gt;any&lt;/em&gt; junior position. I wanted to find a job where I could stay for a good long while, so I ended up &lt;em&gt;not&lt;/em&gt; applying for most junior positions that I ran across.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I stayed away from internships, contracts, and other temporary or low-pay arrangements.&lt;/li&gt;
&lt;li&gt;I stayed away from tiny startups where I would be the only developer—that's not a junior role.&lt;/li&gt;
&lt;li&gt;I stayed away from positions that involved other backend frameworks besides Rails. I'd like to stick to Ruby, at least on the backend, because that's what I most enjoy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yes, I looked for a junior position and I'm proud of it!&lt;/p&gt;

</description>
      <category>career</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>How to contribute to open source: a guide for Rails beginners</title>
      <dc:creator>Felipe Vogel</dc:creator>
      <pubDate>Sat, 04 Dec 2021 14:40:54 +0000</pubDate>
      <link>https://dev.to/fpsvogel/how-to-contribute-to-open-source-a-guide-for-rails-beginners-31oj</link>
      <guid>https://dev.to/fpsvogel/how-to-contribute-to-open-source-a-guide-for-rails-beginners-31oj</guid>
      <description>&lt;p&gt;
  &lt;strong&gt;Table of Contents&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;Find a project&lt;/li&gt;
&lt;li&gt;Set up the project on your local machine&lt;/li&gt;
&lt;li&gt;Become familiar with the codebase&lt;/li&gt;
&lt;li&gt;Find and fix an issue&lt;/li&gt;
&lt;li&gt;
Conclusion
&lt;/li&gt;
&lt;/ul&gt;




&lt;/p&gt;
&lt;p&gt;Here's how I recently got started contributing to open-source Ruby on Rails projects. I'm pretty new to Rails, so if I can do it then you can too! (For more tips for beginning Rubyists, see &lt;a href="https://github.com/fpsvogel/learn-ruby-and-cs"&gt;my study plan&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Find a project
&lt;/h2&gt;

&lt;p&gt;Here are a few beginner-friendly projects that I've come across. If you know of others, please let me know in the comments!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/discourse/discourse"&gt;Discourse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lobsters/lobsters"&gt;Lobsters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rubyforgood.org/"&gt;Ruby for Good&lt;/a&gt; makes software for nonprofit organizations. Each of their projects has a Slack community, so they're extra easy to get into.

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rubyforgood/human-essentials"&gt;Human Essentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubyforgood/casa"&gt;CASA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubyforgood/circulate"&gt;Circulate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rubyforgood/inkind-admin"&gt;InKind Admin&lt;/a&gt; and &lt;a href="https://github.com/rubyforgood/inkind-volunteer"&gt;InKind Volunteer&lt;/a&gt; (Rails + React)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to widen your search, explore the resources at &lt;a href="https://www.firsttimersonly.com/"&gt;First Timers Only&lt;/a&gt;. As you consider projects to contribute to, keep these questions in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the project active? Does it have recent activity and frequent commits?&lt;/li&gt;
&lt;li&gt;Does the &lt;code&gt;README.md&lt;/code&gt; have beginner-friendly instructions?&lt;/li&gt;
&lt;li&gt;Are there a variety of issues tagged "Good First Issue" or something similar?&lt;/li&gt;
&lt;li&gt;Are you interested in helping the project succeed?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set up the project on your local machine
&lt;/h2&gt;

&lt;p&gt;Once you've chosen a project, follow the setup instructions in &lt;code&gt;README.md&lt;/code&gt; or &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;. You will probably run into problems; use your Google-fu to solve them. For example, here were my setup problems in Ubuntu in WSL2, for two of the projects listed above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Circulate:&lt;/strong&gt; The &lt;code&gt;bin/webpack-dev-server&lt;/code&gt; command didn't work until I &lt;a href="https://stackoverflow.com/a/69050300/4158773"&gt;downgraded to a previous version&lt;/a&gt;. Also, &lt;code&gt;chromedriver&lt;/code&gt; (for system tests) is not very straightforward to set up in WSL. The guide that worked for me is &lt;a href="https://linuxtut.com/en/c4d4ed7054b2ada463d6/"&gt;this one&lt;/a&gt; supplemented with &lt;a href="https://www.how2shout.com/how-to/use-gdebi-install-google-chrome-ubuntu-linux.html"&gt;this other one&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lobsters:&lt;/strong&gt; The &lt;code&gt;mysql2&lt;/code&gt; gem wasn't installing properly. It turns out I needed to first install MySQL (duh). So I followed &lt;a href="https://ostechnix.com/how-to-use-mysql-with-ruby-on-rails-application/"&gt;this guide&lt;/a&gt;, adding the extra step of &lt;code&gt;sudo service mysql start&lt;/code&gt; after installing the MySQL packages. I also had to &lt;a href="https://superuser.com/questions/980841/why-is-mysqld-pid-and-mysqld-sock-missing-from-my-system-even-though-the-val"&gt;create some missing files&lt;/a&gt; for MySQL and &lt;a href="https://stackoverflow.com/a/42742610/4158773"&gt;create a new MySQL user&lt;/a&gt; for the databases used by Rails. Oh, and I had to &lt;a href="https://stackoverflow.com/a/38538641/4158773"&gt;disable passwords&lt;/a&gt; before creating the new user, otherwise it couldn't be accessed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Become familiar with the codebase
&lt;/h2&gt;

&lt;p&gt;Poke around and get a feel for what the app does and how it works. Here are some good starting points in a Rails app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the readme (of course)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config/routes.rb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the Gemfile&lt;/li&gt;
&lt;li&gt;&lt;code&gt;db/schema.rb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Find and fix an issue
&lt;/h2&gt;

&lt;p&gt;You can follow roughly these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sure you've read the project's &lt;code&gt;README.md&lt;/code&gt; and (if it has one) &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Find an issue that is well-described and seems simple to fix. Often (but not always) these are tagged as "Good First Issue".&lt;/li&gt;
&lt;li&gt;At this point, some projects prefer that you claim the issue or leave a comment. Be sure to follow the project's contributing guidelines. Once you have the OK from the project maintainers, or if there are no pre-contributing steps, then it's time to work on the issue.&lt;/li&gt;
&lt;li&gt;Reproduce the issue on your local machine.&lt;/li&gt;
&lt;li&gt;Write a test that fails because of the issue. (Not all projects require this, but it's a good rule of thumb.)&lt;/li&gt;
&lt;li&gt;Fix the issue, and make sure your new test passes.&lt;/li&gt;
&lt;li&gt;Send back your fix by creating a PR (pull request). To learn how to make a PR, follow the steps in &lt;a href="https://github.com/firstcontributions/first-contributions"&gt;First Contributions&lt;/a&gt;. &lt;a href="https://gist.github.com/Chaser324/ce0505fbed06b947d962"&gt;Here is another guide&lt;/a&gt; with a few extra steps that are good to keep in mind. Also, if you find that you've cloned a project's repo before forking it, &lt;a href="https://gist.github.com/jagregory/710671"&gt;this guide&lt;/a&gt; explains how to get back on track by making your local copy point to your fork.&lt;/li&gt;
&lt;li&gt;Patiently wait for feedback from the project maintainers, and respond if they ask for more input from you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If all goes well, your pull request will be accepted and you will have made your first contribution to open source! 🎉 From there you can keep an eye out for new issues in your favorite projects so that you can make even more contributions.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>rails</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
