<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[wasabigeek's blog]]></title><description><![CDATA[A blog by a Business grad turned Software Engineer, mostly relating to Ruby and Rails. Nothing to do with actual wasabi.]]></description><link>https://www.wasabigeek.com</link><generator>GatsbyJS</generator><lastBuildDate>Sat, 15 Mar 2025 07:21:36 GMT</lastBuildDate><item><title><![CDATA[Using the XReal AIR 2 Pros for Remote Work (Programming)]]></title><description><![CDATA[I’d been pondering adding a pair of AR glasses to my remote work setup, as an alternative to a portable monitor. After reading positive…]]></description><link>https://www.wasabigeek.com/blog/using-the-xreal-air-2-pros-for-remote-work-programming/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/using-the-xreal-air-2-pros-for-remote-work-programming/</guid><pubDate>Sat, 31 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;I’d been pondering adding a pair of AR glasses to my remote work setup, as an alternative to a portable monitor.&lt;/strong&gt; After reading positive reviews and physically trying the XREAL Airs in Japan, I picked up a pair when they went on sale — just in time for a two-week trip where I’d be working remotely. The verdict after two weeks? I’ll keep using them for my travel work setup, but I’m not giving up my 27-inch monitor at home.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f16d227f9aa71964ec1d91be037ced22/afb4a/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAEDAgT/xAAUAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAGckyuYyI//xAAYEAACAwAAAAAAAAAAAAAAAAAAAhESE//aAAgBAQABBQJlzaWYsNNbLnnJ/8QAFREBAQAAAAAAAAAAAAAAAAAAERD/2gAIAQMBAT8BSf/EABYRAQEBAAAAAAAAAAAAAAAAAAEQEv/aAAgBAgEBPwHIz//EAB0QAAICAQUAAAAAAAAAAAAAAAABAhEiECEyYYH/2gAIAQEABj8C5Iar29LcnJ9meVmzP//EABsQAQADAAMBAAAAAAAAAAAAAAEAESExQVGR/9oACAEBAAE/IX0FfCCQGFw4lPUoIg2PD5FLhseq2X1i+z//2gAMAwEAAgADAAAAEPAf/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxC8JkZ//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERMf/aAAgBAgEBPxDY0Q//xAAcEAEBAAMBAAMAAAAAAAAAAAABEQAhMUGBkfD/2gAIAQEAAT8QVm+WgF5+8x2Edw4fJM2eJ4vMYkBtg6pXmH6nCCZKganPrEAxDURz/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;XREAL Air 2 Pros plugged in without the nosepads.jpeg&quot;
        title=&quot;XREAL Air 2 Pros plugged in without the nosepads.jpeg&quot;
        src=&quot;/static/f16d227f9aa71964ec1d91be037ced22/1c72d/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg&quot;
        srcset=&quot;/static/f16d227f9aa71964ec1d91be037ced22/a80bd/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 148w,
/static/f16d227f9aa71964ec1d91be037ced22/1c91a/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 295w,
/static/f16d227f9aa71964ec1d91be037ced22/1c72d/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 590w,
/static/f16d227f9aa71964ec1d91be037ced22/a8a14/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 885w,
/static/f16d227f9aa71964ec1d91be037ced22/fbd2c/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 1180w,
/static/f16d227f9aa71964ec1d91be037ced22/afb4a/XREAL_Air_2_Pros_plugged_in_without_the_nosepads.jpg 4284w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-setup&quot; aria-label=&quot;the setup permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Setup&lt;/h2&gt;
&lt;p&gt;I got the XREAL Air 2 Pros. The key difference from the base model is that this comes with electrochromic lenses, allowing you to change the tint with the press of a button. The lenses got dark enough that I could skip using the included lens cover.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/9fefee3ccdcdc5fdb1fc49f49018137e/XREAL_Air_chromatic_lenses.gif&quot; alt=&quot;XREAL Air chromatic lenses.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;In addition, I also purchased:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;the Beam device&lt;/strong&gt; - I’m not cleared to install the Nebula software on my work laptop. That would have allowed for screen anchoring, which the wisdom of Reddit said was important. So I got the Beam just for anchoring 😑.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;prescription lenses from &lt;a href=&quot;https://honsvr.com/product/oculus-quest-2-prescription-lens-adapter/?gad_source=1&amp;#x26;gclid=Cj0KCQjw28W2BhC7ARIsAPerrcLYM1RZBwfY9rxZkdQlTJuISora-S-vfdDIpMx0emgGkGgoRquoPhMaAhSvEALw_wcB&quot;&gt;HonsVR&lt;/a&gt;&lt;/strong&gt; (~US$76 with SPH prescription and a blue light filter). I’m not a fan of contact lenses, so this was the only way for me to comfortably use the glasses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/45612179facb1265fe0c08f7d7190404/9568a/xreal_air2_pros_with_prescription_lenses.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAFajyTNAf/EABoQAQACAwEAAAAAAAAAAAAAAAIBBAADEiH/2gAIAQEAAQUCNv2bQjIsdZOkpPUVIjg//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGxAAAgIDAQAAAAAAAAAAAAAAAAECEQMiMVH/2gAIAQEABj8CanFnGa45sv0vjKR//8QAGxABAAMBAAMAAAAAAAAAAAAAAQARMUEhUYH/2gAIAQEAAT8hAAEamUvypWsTPBHjXpJWnoy4e11Wf//aAAwDAQACAAMAAAAQ7/8A/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAQADAQADAAAAAAAAAAAAAREAITFRQXGx/9oACAEBAAE/ENXLQCk6d3vDSmgx+b7ysFNIIPn5h2Io5G93KXB3KEgCBDT5lcNKBtXP/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;xreal_air2_pros_with_prescription_lenses.jpeg&quot;
        title=&quot;xreal_air2_pros_with_prescription_lenses.jpeg&quot;
        src=&quot;/static/45612179facb1265fe0c08f7d7190404/1c72d/xreal_air2_pros_with_prescription_lenses.jpg&quot;
        srcset=&quot;/static/45612179facb1265fe0c08f7d7190404/a80bd/xreal_air2_pros_with_prescription_lenses.jpg 148w,
/static/45612179facb1265fe0c08f7d7190404/1c91a/xreal_air2_pros_with_prescription_lenses.jpg 295w,
/static/45612179facb1265fe0c08f7d7190404/1c72d/xreal_air2_pros_with_prescription_lenses.jpg 590w,
/static/45612179facb1265fe0c08f7d7190404/a8a14/xreal_air2_pros_with_prescription_lenses.jpg 885w,
/static/45612179facb1265fe0c08f7d7190404/fbd2c/xreal_air2_pros_with_prescription_lenses.jpg 1180w,
/static/45612179facb1265fe0c08f7d7190404/9568a/xreal_air2_pros_with_prescription_lenses.jpg 3024w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;using-the-xreals-unplugged&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#using-the-xreals-unplugged&quot; aria-label=&quot;using the xreals unplugged permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Using the XREALs unplugged&lt;/h2&gt;
&lt;p&gt;Don’t be fooled by the marketing, these glasses will &lt;em&gt;not&lt;/em&gt; pass off as regular sunglasses in spite of the obvious RayBan influences. They are noticeably larger, and sit much further from your face. Face ID wouldn’t work for me, while it does with my regular glasses.&lt;/p&gt;
&lt;p&gt;That said, at 75g, they’re lightweight and comfortable for extended wear. The electrochromic lenses, a key feature of the Pro model, allow you to adjust the tint at the press of a button, eliminating the need for the lens cover. In theory, this lets you move around without having to swap back to your regular glasses.&lt;/p&gt;
&lt;p&gt;However, the displays only cover half the vertical area, which creates an unavoidable horizontal line with different tints above and below. Plus, when unplugged, the glasses remain at a medium tint level, leaving you with sunglasses indoors—not ideal.
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/30b84abcd739ed8d520acc11974db8e5/4239e/XREAL_Airs_displays_in_field_of_view.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEA//EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHCxKJFKQ//xAAZEAACAwEAAAAAAAAAAAAAAAABAgADBCH/2gAIAQEAAQUCq0isHUghtaLn69Su0//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB8QAAEDAwUAAAAAAAAAAAAAAAACERIBAxAhIjGBkf/aAAgBAQAGPwJoHCvDS0upubolj//EABwQAAICAgMAAAAAAAAAAAAAAAERACExUUFhcf/aAAgBAQABPyERMxtGHl6NFGK7aUpElfDMalE57jn/2gAMAwEAAgADAAAAEOw//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHRABAQABBAMAAAAAAAAAAAAAAREAITFBcWGBof/aAAgBAQABPxAaKtbL3rcCIWwUdhzyiRCvS4FFAUSxxrtjVUMkH62xBn//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;XREAL Airs displays in field of view.jpeg&quot;
        title=&quot;XREAL Airs displays in field of view.jpeg&quot;
        src=&quot;/static/30b84abcd739ed8d520acc11974db8e5/1c72d/XREAL_Airs_displays_in_field_of_view.jpg&quot;
        srcset=&quot;/static/30b84abcd739ed8d520acc11974db8e5/a80bd/XREAL_Airs_displays_in_field_of_view.jpg 148w,
/static/30b84abcd739ed8d520acc11974db8e5/1c91a/XREAL_Airs_displays_in_field_of_view.jpg 295w,
/static/30b84abcd739ed8d520acc11974db8e5/1c72d/XREAL_Airs_displays_in_field_of_view.jpg 590w,
/static/30b84abcd739ed8d520acc11974db8e5/a8a14/XREAL_Airs_displays_in_field_of_view.jpg 885w,
/static/30b84abcd739ed8d520acc11974db8e5/fbd2c/XREAL_Airs_displays_in_field_of_view.jpg 1180w,
/static/30b84abcd739ed8d520acc11974db8e5/4239e/XREAL_Airs_displays_in_field_of_view.jpg 2193w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
As a result, I ended up switching back to my regular glasses about half the time. While useful in specific scenarios, they’re not something I’d wear outside or as an everyday replacement for my usual glasses.&lt;/p&gt;
&lt;h2 id=&quot;using-the-xreals-without-the-beam&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#using-the-xreals-without-the-beam&quot; aria-label=&quot;using the xreals without the beam permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Using the XREALs without the Beam&lt;/h2&gt;
&lt;p&gt;When plugged directly into a compatible USB-C device like a Macbook or iPhone 15, the glasses act as an external display, mirroring or extending your device’s screen. The image is sharp and text is very readable, but it can be quite hard to read the edges since those remain in your peripheral vision. The screen moves with your head, so if you want to focus on a corner you have to keep your head still and move just your eyes. As such, it’s great for watching video, but for doing work you’ll have to keep what you’re reading in the center of the screen.&lt;/p&gt;
&lt;p&gt;I also learnt my head moves a lot when working, especially while I’m typing. Since the screen follows these movements, it’s a bit disorienting, bordering on nauseating. Due to this, I rarely used the XREALs without the Beam, save for the occasional video in bed or during a long bus ride. Admittedly, the latter was a cool use case; with the electrochromic lenses, I could adjust the transparency to appreciate the scenery. However, that persistent horizontal line remained a drawback.&lt;/p&gt;
&lt;h2 id=&quot;using-the-xreals-with-the-beam&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#using-the-xreals-with-the-beam&quot; aria-label=&quot;using the xreals with the beam permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Using the XREALs with the Beam&lt;/h2&gt;
&lt;p&gt;The Beam allows you to mirror or extend your device’s display without installing additional software, which sounds great and works, but there are tradeoffs 😞. &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/202c9874738a6b75b9eddf514f4148b1/b89ae/XREAL_Airs_with_Beam.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABYBAQEBAAAAAAAAAAAAAAAAAAIBA//aAAwDAQACEAMQAAABpHbE1hzJ/wD/xAAZEAACAwEAAAAAAAAAAAAAAAABAgAREgP/2gAIAQEAAQUCylHuxioWDJKgbI//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8BqY//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQIBAT8Bi6//xAAbEAADAAIDAAAAAAAAAAAAAAAAARECURJhcf/aAAgBAQAGPwJ72iPJYzovKlpv0SP/xAAZEAEAAwEBAAAAAAAAAAAAAAABABEhQVH/2gAIAQEAAT8hvIa8FxZY9MgSA9mqEDqEvTzeMaYBWXP/2gAMAwEAAgADAAAAEEA//8QAFxEBAAMAAAAAAAAAAAAAAAAAAAERMf/aAAgBAwEBPxAijH//xAAXEQADAQAAAAAAAAAAAAAAAAAAASER/9oACAECAQE/EJHrGf/EACAQAQACAQIHAAAAAAAAAAAAAAEAETEhQVFhcYGRscH/2gAIAQEAAT8QCKQGxR+YPE5FgK7Z6wLG2FW+8QVWKbm8FvB0UcZv3CJE0AqnHQn/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;XREAL Airs with Beam.jpeg&quot;
        title=&quot;XREAL Airs with Beam.jpeg&quot;
        src=&quot;/static/202c9874738a6b75b9eddf514f4148b1/1c72d/XREAL_Airs_with_Beam.jpg&quot;
        srcset=&quot;/static/202c9874738a6b75b9eddf514f4148b1/a80bd/XREAL_Airs_with_Beam.jpg 148w,
/static/202c9874738a6b75b9eddf514f4148b1/1c91a/XREAL_Airs_with_Beam.jpg 295w,
/static/202c9874738a6b75b9eddf514f4148b1/1c72d/XREAL_Airs_with_Beam.jpg 590w,
/static/202c9874738a6b75b9eddf514f4148b1/a8a14/XREAL_Airs_with_Beam.jpg 885w,
/static/202c9874738a6b75b9eddf514f4148b1/fbd2c/XREAL_Airs_with_Beam.jpg 1180w,
/static/202c9874738a6b75b9eddf514f4148b1/b89ae/XREAL_Airs_with_Beam.jpg 5126w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
Compared to a direct connection, the image is noticeably blurrier, though still usable. There’s a subtle flickering, especially apparent on dark backgrounds at lower brightness levels.&lt;/p&gt;
&lt;p&gt;In most modes, you can adjust the screen size. I primarily used the default “120-inch” setting, which, in practice, resembled a 27-inch monitor about a meter away—quite satisfactory. Larger screen sizes are possible, but due to the limited field of view, the screens get cropped, requiring you to move your head to see the edges.&lt;/p&gt;
&lt;p&gt;The resolution is 1080p, which was acceptable for me, considering my home monitor had been set to 1080p for the past two years without issue. There are three display modes, but I mainly used “Body Anchor.” “Smooth Follow” doesn’t provide a clear view of the edges, and “Sideview” mode seems intended for multitasking, which didn’t feel particularly safe or practical.&lt;/p&gt;
&lt;p&gt;It appears the Beam can’t mirror and charge simultaneously. In practice, this wasn’t a significant concern, as the Beam’s battery lasts several hours. As long as I remembered to charge it between sessions, it sufficed.&lt;/p&gt;
&lt;p&gt;With screen anchoring, I was somewhat to use my Macbook’s physical display as a secondary screen. Whe the wall was a flat colour, I could reduce the dimming on the lenses while keeping the XREAL display readable and allowing me to read the physical display. However, the aforementioned caveats (the horizontal line and tinting) meant I only used it for secondary functions e.g. keeping an eye on Slack or having Spotify open.&lt;/p&gt;
&lt;p&gt;Occasionally, I noticed subtle drifting of the screen when anchored. Restarting the Beam didn’t resolve this; I had to re-anchor the screen periodically until the issue subsided. Also, the Beam gets hot pretty quick, and the built-in fans will start whirling. It’s not crazy loud but noticeable.&lt;/p&gt;
&lt;p&gt;UPDATE 2025-02: my beam has started making a weird beeping sound, I’ll have to ask XREAL what’s going on here.&lt;/p&gt;
&lt;h2 id=&quot;other-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-notes&quot; aria-label=&quot;other notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other Notes&lt;/h2&gt;
&lt;p&gt;Since you don’t need to physically position an external monitor, the setup occupies much less space and is more ergonomic, allowing you to look forward at a floating screen instead of hunching over a desk.&lt;/p&gt;
&lt;p&gt;My remote setup is currently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a 14-inch Macbook Pro&lt;/li&gt;
&lt;li&gt;a Roost laptop stand&lt;/li&gt;
&lt;li&gt;the Logitech POP mouse&lt;/li&gt;
&lt;li&gt;a folding split keyboard&lt;/li&gt;
&lt;li&gt;It’s on the left below, in contrast to my wife’s setup: &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0c7d4f926b217ea66a73d0b9ae290516/b7c1f/XREAL_Air_setup.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwAE/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAARANorZaX//EABoQAAMAAwEAAAAAAAAAAAAAAAECAwAEERL/2gAIAQEAAQUCr5ejCeJtyCklHW6MCeH/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAdEAACAQUBAQAAAAAAAAAAAAAAAQIREiFRcQMi/9oACAEBAAY/AotUe2SbjPAlbLA1op6Y4j5tpw//xAAcEAACAgIDAAAAAAAAAAAAAAABEQAxIUFRYYH/2gAIAQEAAT8hwrKAYee5CbzANAAWpdll5EkIaMGi8Lj/2gAMAwEAAgADAAAAEKA//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxCJumn/xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQIBAT8QFDCwv//EAB0QAQADAAEFAAAAAAAAAAAAAAEAESExQVFhcfD/2gAIAQEAAT8QKTYNCN5W9phk1ahgY+buGakSlwe4TV+AMWYIDZlb1lbYm1es/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;XREAL Air setup.jpeg&quot;
        title=&quot;XREAL Air setup.jpeg&quot;
        src=&quot;/static/0c7d4f926b217ea66a73d0b9ae290516/1c72d/XREAL_Air_setup.jpg&quot;
        srcset=&quot;/static/0c7d4f926b217ea66a73d0b9ae290516/a80bd/XREAL_Air_setup.jpg 148w,
/static/0c7d4f926b217ea66a73d0b9ae290516/1c91a/XREAL_Air_setup.jpg 295w,
/static/0c7d4f926b217ea66a73d0b9ae290516/1c72d/XREAL_Air_setup.jpg 590w,
/static/0c7d4f926b217ea66a73d0b9ae290516/a8a14/XREAL_Air_setup.jpg 885w,
/static/0c7d4f926b217ea66a73d0b9ae290516/fbd2c/XREAL_Air_setup.jpg 1180w,
/static/0c7d4f926b217ea66a73d0b9ae290516/b7c1f/XREAL_Air_setup.jpg 5712w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the case and Beam, the setup is only slightly lighter than a portable monitor but significantly easier to pack and transport. Plus, you technically get a larger display, as most portable monitors are around 16 inches.&lt;/p&gt;
&lt;p&gt;I still removed the glasses for video calls, though they could serve as an interesting conversation starter.&lt;/p&gt;
&lt;p&gt;I haven’t extensively tested Nebula. I briefly tried it on my personal laptop; multiple screens seemed cool, but the quality of individual displays appeared similar to the Beam.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#summary&quot; aria-label=&quot;summary permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Summary&lt;/h2&gt;
&lt;p&gt;I couldn’t work a full day in them, but think I got enough benefit out of half a day that I could see myself continuing to use this setup when travelling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The good&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compact and easy to bring around&lt;/li&gt;
&lt;li&gt;A relatively big screen (though 120 inch is a bit misleading, in practice it was like using a 27-inch monitor on a desk)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The meh&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Still can’t beat an actual desktop monitor in terms of field of view, comfort and clarity&lt;/li&gt;
&lt;li&gt;Beam is kinda annoying to manage&lt;/li&gt;
&lt;li&gt;Pricier than a portable monitor, especially if you need the Beam and prescription lenses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you found this useful and are considering a purchasing the XREAL Air 2s, consider supporting my blog by using one of the following affiliate links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://s.lazada.sg/s.1WjQN?cc&quot;&gt;Lazada Singapore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[How I'm dealing with Self-Doubt as a Software Developer]]></title><description><![CDATA[As a software developer, I’ve found self-doubt tends to intensify when: comparing oneself with others e.g. can I really be as good as person…]]></description><link>https://www.wasabigeek.com/blog/how-i-m-dealing-with-self-doubt-as-a-software-developer/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/how-i-m-dealing-with-self-doubt-as-a-software-developer/</guid><pubDate>Mon, 24 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a software developer, I’ve found self-doubt tends to intensify when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;comparing oneself with others e.g. can I really be as good as person X?&lt;/li&gt;
&lt;li&gt;getting some negative feedback, especially from people I’m unfamiliar with (I think this is close to imposter syndrome, where I’m worrying about being judged)&lt;/li&gt;
&lt;li&gt;looking at something I don’t know much about or struggle to understand&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is an unstructured collection of resources and thought processes I’ve found helpful when that happens.&lt;/p&gt;
&lt;h2 id=&quot;remember-we-dont-see-the-journey-they-took-to-get-there&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#remember-we-dont-see-the-journey-they-took-to-get-there&quot; aria-label=&quot;remember we dont see the journey they took to get there permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Remember we don’t see the journey they took to get there&lt;/h2&gt;
&lt;p&gt;When comparing yourself to others, it’s super easy to be focused on how good they are now, without accounting for time taken to get there (and how much they struggled, just like you might be struggling now).&lt;/p&gt;
&lt;p&gt;I paired recently with &lt;a href=&quot;https://twitter.com/vinistock&quot;&gt;@vinistock&lt;/a&gt; and was amazed by (from my perspective) his deep knowledge of Ruby internals like singleton classes and module hooks. What helped demystify it was asking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;did he have this knowledge when he joined his current team a year ago? Answer: No, in fact, it was a struggle to learn.&lt;/li&gt;
&lt;li&gt;what resources did he use to get better, which he graciously shared. This helped me to chart a sort of “path to improvement”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are probably geniuses out there who grasp things immediately, but for the majority of us, it’s about working to get better, and hanging in there.&lt;/p&gt;
&lt;h2 id=&quot;realising-they-made-some-learning-tradeoffs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#realising-they-made-some-learning-tradeoffs&quot; aria-label=&quot;realising they made some learning tradeoffs permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Realising they made some learning tradeoffs&lt;/h2&gt;
&lt;p&gt;Another one when comparing yourself to others. &lt;a href=&quot;https://twitter.com/dan_abramov&quot;&gt;@dan_abramov&lt;/a&gt; (a public figure in the React community) has written about &lt;a href=&quot;https://overreacted.io/things-i-dont-know-as-of-2018/&quot;&gt;things he doesn’t know&lt;/a&gt;, which helped to break a false notion that because I didn’t know “everything”, I was behind.&lt;/p&gt;
&lt;p&gt;It can be easy for us to narrowly focus on what our heroes know deeply about, and forget that they still don’t know everything, and likely made learning tradeoffs to get there.&lt;/p&gt;
&lt;p&gt;I still like the idea of being able to know everything, but am a lot more zen about it now. I try to remember that it’s likely something that &lt;em&gt;can&lt;/em&gt; be learnt, but I don’t &lt;em&gt;have&lt;/em&gt; to learn it now.&lt;/p&gt;
&lt;h2 id=&quot;getting-more-feedback&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#getting-more-feedback&quot; aria-label=&quot;getting more feedback permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Getting more feedback&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/bethcodes&quot;&gt;@bethcodes&lt;/a&gt; has characterised &lt;a href=&quot;https://web.archive.org/web/20230215110200/https://blog.bethcodes.com/confidence-through-feedback&quot;&gt;imposter syndrome as a lack of feedback&lt;/a&gt;, and presented the dangers of choosing not to seek it. To get feedback, we need to put ourselves out there (be it “asking stupid questions”, taking projects outside our comfort zone, or asking directly for it). That’s risky - we can look stupid, get laughed at, and the like. But when we get constructive feedback, it leads to one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;learning what you can improve in&lt;/li&gt;
&lt;li&gt;learning that you’re not as terrible as you think&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My favourite passage from @bethcode’s post:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On and on, my stupidity was laid bare for the world to see and it turned out it didn’t matter. No one cared, and when I took the feedback on board for next time they thought it was awesome.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even with rational understanding of the above, it takes me time and repetition to get comfortable with putting myself out there (be it “asking stupid questions” or seeking feedback). Psychological safety is an important pre-requisite, and I think it’s both trying to find a good environment, as well as testing the waters to see if whether people truly practice a safe environment.&lt;/p&gt;
&lt;p&gt;On a somewhat related note, working with different people helped as well, as it became clearer that the positive feedback wasn’t one-off. Plus, if many different people are saying similar things, it becomes harder for you to refute. For me, this became clearer when I changed jobs - I really enjoyed my previous company actually, but kept wrestling with my standing in the industry. (I wouldn’t take this at face value though, depending on you / your company you might be able to achieve similar results without leaving!)&lt;/p&gt;
&lt;h2 id=&quot;reframing-as-something-that-can-be-improved-growth-mindset&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#reframing-as-something-that-can-be-improved-growth-mindset&quot; aria-label=&quot;reframing as something that can be improved growth mindset permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Reframing as something that can be improved (Growth Mindset)&lt;/h2&gt;
&lt;p&gt;I feel a little cheesy saying this - reframing the subject of my imposter syndrome as something improvable has had the most impact over the last year or so. A key catalyst for this was reading Carol Dweck’s Mindset.&lt;/p&gt;
&lt;p&gt;One particular example from the book that sticks with me - children were given a set of puzzles to see how they dealt with difficulty:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Confronted with the hard puzzles, one ten-year-old boy pulled up his chair, rubbed his hands together, smacked his lips, and cried out, “I love a challenge!”
…
What’s wrong with them? I wondered. I always thought you coped with failure or you didn’t cope with failure. I never thought anyone loved failure.
…
Not only weren’t they discouraged by failure, they didn’t even think they were failing. They thought they were learning.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When inadequacy or jealousy creeps in, I try now to think of the subject as something I can get better in, as opposed to being something impossible. I avoid thinking about whether I can achieve the same level of expertise, just reminding myself that I can get better. I literally imagine and try to emulate a kid saying, “Whoa, that’s awesome! Let me try.”&lt;/p&gt;
&lt;p&gt;Again, it’s not something that changed overnight, and I’m still working on it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Visitor Pattern in Ruby: Examples from Rubocop]]></title><description><![CDATA[In this post, we’ll learn about the Visitor design pattern and it’s tradeoffs by looking at a real world example: Rubocop, a Ruby linting…]]></description><link>https://www.wasabigeek.com/blog/visitor-pattern-in-ruby-rubocop/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/visitor-pattern-in-ruby-rubocop/</guid><pubDate>Tue, 09 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post, we’ll learn about the Visitor design pattern and it’s tradeoffs by looking at a real world example: Rubocop, a Ruby linting and formatting library.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-visitor-design-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-the-visitor-design-pattern&quot; aria-label=&quot;what is the visitor design pattern permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is the Visitor design pattern?&lt;/h2&gt;
&lt;p&gt;The visitor design pattern separates the operation to be performed from a complex object structure (e.g. a graph whose nodes have many types).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 385px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/80a5d06b70a8b1e494163786d54394f5/409e6/visitor.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86.48648648648648%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADv0lEQVQ4y21U30sjVxQ+MSJE31rLgi6UPrR9KCvbItv9D1rwbZ/qQ/tQ2G6ffCkiaLEgPhRFUDCKJkIFCSEiIvqi0Qjij1iNJpnRuTNzZyZ3dOLkJ4Zau5pNbjmDCVvshcuZezjnu+ee7zsDAOAGgM6GhobnLpfrawD4CgCa4X+Wz+dz9vr6OgwPD6OrAQA+A4Ang4ODsLa2BtDa2grt7e0tAPBRU1NTu8fj+bClpQU8Hs8jwOnpaZiamgKv19s8Pz//NBQKtc3Ozn7i9Xo/DgaDbX6//wn4/X5XIBCA7u5u9+TkJPT09MDAwICrt7f3EaBhGG5d10GW5e/v7u7+yefzV4lEwjZN0y6Xy9f5fP5PSCQSsLu722nbNmOMrfX19XkmJibQ9wiQMdbIGEPgN8VikedyOX5xccGvrq54qVTitm2nQFEUEAShj1LKdV3ngiB8Kooi6LreWANaWlpyrCzLbk3TQNO014lEgu/t7d2LoliJxWLvTk5OOCFEgZWVFRgaGmrLZDI+SukvANAYiUSgWq0C57y+cWma5gBSSl9fXl5yxljZsqyqaZoVPBuGQTHYhaxiUqFQwLzWg4ODb23bfoG+SqXiqgFSSp0eYoWKovBkMnlPKa1IkvTu/Pyca5qm1m53KJ2bm4P9/f1vDMNwbt/Y2PgyHA5DNBpFeThtMAwD7RvGGMc2pVIpp1WmaWJOCvvyq67rx8fHxx22bUMoFHqlaRoGvN3a2urY3t6Gw8NDB1CSJLcsy0AI+UFRlLeiKNqU0pwkSVlVVUuU0iPAUi3L4rFYbO6h2mafz/cqEom83NzchJ2dnfqTx8fHYWxsDEZHRz8IBALPw+Hws2g0+gzt4uJix8zMzOeQSqV+z+VyJ/F4/EUqlXIS3yfjfVIYY26Ujaqq35XL5UyhUFDj8biWTqcp5/yiWCxu1xNKpRKMjIxAV1dX48LCQicCLC8vO/vo6Og/OtR1/WfUYSaTcfqXTqf59fU16vHCYfkB1I2WEPJHNpvlkiT99uBvWF1dfcQyEiKK4j1KRlGUMp41TVMc0eLo1SpVVTWBmiKELNUA8WdQA6zpEAEIIQhUlWUZQZF1Wh8ry7JclmUhk19ks9mBZDL5FCXCGKuToqpqvUJCCBJZliSpKghCJZlMckVR1DrgQzWufD7vfCNBNzc3rtvb2zopuq67H3T4E84wY6yCCkFNojVNU3MCg8Fg/cmmaWJFbkEQXJlMBlCb/f39TtzZ2ZmbEIJS+xHldnp6+hch5G9BEG5wWhRFEf8FwsopIuWgP/oAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;visitor.png&quot;
        title=&quot;visitor.png&quot;
        src=&quot;/static/80a5d06b70a8b1e494163786d54394f5/409e6/visitor.png&quot;
        srcset=&quot;/static/80a5d06b70a8b1e494163786d54394f5/12f09/visitor.png 148w,
/static/80a5d06b70a8b1e494163786d54394f5/e4a3f/visitor.png 295w,
/static/80a5d06b70a8b1e494163786d54394f5/409e6/visitor.png 385w&quot;
        sizes=&quot;(max-width: 385px) 100vw, 385px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This makes it easy to introduce new operations (“visitors”) for the same structure, but comes at a cost - changing the object structure requires all visitors to be changed. So this pattern works best when changes to the underlying structure are rare, and we expect many different operations to be introduced.&lt;/p&gt;
&lt;h2 id=&quot;what-is-rubocop&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-rubocop&quot; aria-label=&quot;what is rubocop permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is Rubocop?&lt;/h2&gt;
&lt;p&gt;Rubocop is a linter&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; and formatter, which is a fancy way of saying that it checks the raw source code against a set of rules (e.g. for common errors or conformance to a style guide) and can autocorrect it. Each rule in Rubocop is called a “cop” and follows a visitor-inspired pattern.&lt;/p&gt;
&lt;p&gt;Note that Rubocop doesn’t follow the classical visitor implementation&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; exactly, but separates concerns similarly. We’ll look at how it tweaks the pattern, before describing the tradeoffs with real-life examples.&lt;/p&gt;
&lt;h2 id=&quot;object-structure-abstract-syntax-tree-ast&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#object-structure-abstract-syntax-tree-ast&quot; aria-label=&quot;object structure abstract syntax tree ast permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Object Structure: Abstract Syntax Tree (AST)&lt;/h2&gt;
&lt;p&gt;Before running any cops, Rubocop first uses the &lt;a href=&quot;https://github.com/rubocop/rubocop-ast&quot;&gt;rubocop-ast&lt;/a&gt; library to create an Abstract Syntax Tree representing your Ruby code. This makes the responsibility of cops much simpler: they don’t have to worry about syntax correctness, and because code is pre-grouped and categorised in the tree, it’s easier to know when a linting rule should be triggered.&lt;/p&gt;
&lt;p&gt;Let’s look at a simple example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;rubocop-ast&apos;&lt;/span&gt;&lt;/span&gt;

code_string &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal heredoc-string&quot;&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;CODE&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;
  private def sum(a, b)
    a + b
  end

  sum(1, 2)
&lt;/span&gt;&lt;span class=&quot;token delimiter&quot;&gt;&lt;span class=&quot;token symbol&quot;&gt;CODE&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# =&gt; &quot;private def sum(a, b)\n a + b\nend\n\nsum(1, 2)\n&quot;&lt;/span&gt;

processed_source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RuboCop&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;AST&lt;/span&gt;&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ProcessedSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code_string&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
processed_source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ast
&lt;span class=&quot;token comment&quot;&gt;# =&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# s(:begin,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#   s(:send, nil, :private,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#     s(:def, :sum,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#       s(:args,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#         s(:arg, :a),&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#         s(:arg, :b)),&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#       s(:send,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#         s(:lvar, :a), :+,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#         s(:lvar, :b))),&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#   s(:send, nil, :sum,&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#     s(:int, 1),&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;#     s(:int, 2)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The parsed AST breaks down the code into relevant constructs based on Ruby’s syntax. We can visualize the above:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fa167f4ce62165dacc4468696a171423/8ce52/ruby_ast_example.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 79.72972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAADwklEQVQ4y3VSfWgbZRh/7rK0OqaIKzIYdog4HK66OZ3raFe66Ryof8iCImwgsvmPokyRgaKIGyobKwPFqYigbDrYUPBr0dZ+JLm0Ta5JmuSSXHNJ7vsub+6SXK+xWiUn7zUJ69AHHp7nfT5+7/N73hd29D8GO/cedPWe7bsBi2edF36dKRGc7QBrO92hpKIHk9qJQEKCsUjBg2sIgnBr79+9Hx4ePAQPDRyCHXsehY44jkNgPTA82Im9M3LRjQcS8rEIW++boHl8dnO7HhlwfZzn6w5oyw7ECotrAF3t6dnYDQCuer1d2Ha1c5vv3HJTO3d7zx3Y9+B4Tm8CZziQkv4C+M5Pkf4QA8F55bNAQnk/wqhw6fsxz11bt7sXbdzUC1zF8Xx1dYzE51tuvQ3Of36ZZJEDVMbsDc7Ls4Gk3jeVUGA0UiTxzWS8sAjTTPksldLfTJbqbmw6vwxPHT7igtZaNPf090M6nXZp0qID0fzi5nBa91NMZVuIMaHuOGR7J542rbb/2tvn4JWTpzo7w9bn88HQ0NCa2HV9pGslSUoihO7LZLJf5LLMqVDWHqRSWpytOzf/GMp3GsapOPClIiCEQBTFK4qifFgscNeyRf10OGP8EmWrH1Bp5AfTNL/mef5uUZJOyhJ/3B+Rd0VyxqULlyfXY6CDT/o8N05jGMZpVdNeKuvauQWh8iLN2SNznPUyzVkjbkGpVIJms3kjBWLrtj73JfGr/tmiuW9gr5tfWVmBf/5eAd1srO1rNpte0zSBF4Rjksj/HmXNZ6m0nmKrzoYfghx+kO7gvBIOpbTnwykVJmMC3hXZaDTWoUoFVFXdh1CFVhRlRBTFCSiXy+QqoHigrKvvzmYr/VRa/0hoOF0TzB9gO45nOoPO0Hlr+OdQDqikSibicxiIwH2SJN1bq9XOaJp2VNW09zqjyrIMzRbVNuXWn/det4b1X377E5w9fwHM+pIbw6DYNhoNWFpaWl32DGuR4Wwd5jhr/2y2komytU9mMmgqulC7OJ3Wr9Rr5mhRLH8cZhBL560H40Ub5vIW2QLEA5C2bbsKsVgMAoxFTCYNmEpXt/wWFV8fT6AnRqPS8YlE+Rn/TOk526qe4AT96Wuz4htBprZpKmXCZNIgaJp2+/Fnj0ajkMlkVjkJRZbIMvOQz6V7hRL3qiwUHy/kcy9osnCYW8j6NE2FCDW+gS/m30Ka2LPcsKCiy/C/IssyKUkSSLI8bBjGgqZpnyKEggihqwihb6rVKvC88IBhmqYoSjtNswq6rpP/BfYvm0XPd5EAuMYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ruby_ast_example.png&quot;
        title=&quot;ruby_ast_example.png&quot;
        src=&quot;/static/fa167f4ce62165dacc4468696a171423/fcda8/ruby_ast_example.png&quot;
        srcset=&quot;/static/fa167f4ce62165dacc4468696a171423/12f09/ruby_ast_example.png 148w,
/static/fa167f4ce62165dacc4468696a171423/e4a3f/ruby_ast_example.png 295w,
/static/fa167f4ce62165dacc4468696a171423/fcda8/ruby_ast_example.png 590w,
/static/fa167f4ce62165dacc4468696a171423/8ce52/ruby_ast_example.png 681w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For now, it’s sufficient to know that the object structure is basically a tree - don’t worry too much if you don’t understand exactly how it works. (For a good overview of how this AST fits into the Ruby interpretation process, check out this &lt;a href=&quot;https://youtu.be/ySuMOEVLaMw&quot;&gt;great video&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/OngMaple&quot;&gt;@OngMaple&lt;/a&gt;. You can also look &lt;a href=&quot;https://github.com/rubocop/rubocop-ast/blob/master/docs/modules/ROOT/pages/node_types.adoc&quot;&gt;here&lt;/a&gt; for a list of possible node types 🤯.)&lt;/p&gt;
&lt;h2 id=&quot;visitor-cops&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#visitor-cops&quot; aria-label=&quot;visitor cops permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Visitor: Cops&lt;/h2&gt;
&lt;p&gt;Each linting or formatting rule in Rubocop is encapsulated in a “cop”, which is essentially a visitor. For example, let’s look at the &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/layout/def_end_alignment.rb&quot;&gt;DefEndAlignment&lt;/a&gt; cop. By default, this checks that &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt; is aligned to &lt;code class=&quot;language-text&quot;&gt;def&lt;/code&gt; at the start of the line, e.g.:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# bad&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;foo&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# good&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;foo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each cop defines &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods, which the traversal mechanism will call when it hits a corresponding AST node. We can see the cop handles 3 node types, &lt;code class=&quot;language-text&quot;&gt;on_def&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;on_defs&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;on_send&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;on_def&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;alias&lt;/span&gt; on_defs on_def

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;on_send&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(&lt;code class=&quot;language-text&quot;&gt;on_send&lt;/code&gt; might seem out-of-place - it’s for handling “def modifiers” e.g. &lt;code class=&quot;language-text&quot;&gt;private&lt;/code&gt; in &lt;code class=&quot;language-text&quot;&gt;private def foo&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;If you recall the AST from before, there weren’t any &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt; nodes, and the structure itself left out indentations and spaces. So how does this linter work? Turns out that &lt;code class=&quot;language-text&quot;&gt;parser&lt;/code&gt; nodes still contain information about the source code (e.g. positions), and cops can use this in tandem with the original source code for linting.&lt;/p&gt;
&lt;p&gt;Of note: this cop also does it’s own little traversal for &lt;code class=&quot;language-text&quot;&gt;on_send&lt;/code&gt;. This is because the &lt;code class=&quot;language-text&quot;&gt;send&lt;/code&gt; node contains the start position of the &lt;code class=&quot;language-text&quot;&gt;private&lt;/code&gt; keyword, but the position of &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt; is still in the  &lt;code class=&quot;language-text&quot;&gt;def&lt;/code&gt; node. Thus, the code accesses &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/133ffce3d3179f6cf2d7750da2a2da0add8c7e41/lib/rubocop/cop/layout/def_end_alignment.rb#L51&quot;&gt;descendants&lt;/a&gt; to look for the actual &lt;code class=&quot;language-text&quot;&gt;def&lt;/code&gt; node. A visual from our previous example:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 212px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/31cc05831c7b3e162da8b84f4e3b6440/9f128/on_send_def_traversal.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.62162162162163%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAADT0lEQVQ4y41TX0xTZxQ/lzKnTz5MZpNpdDAyMrcEzUwajWJ82R6W+LC5CcO9zF4UjAnBB/89eE2JksADxr2Arsw5l2ywlHvL6N1tg7SlVdbeUitevBS95baG9rYUI7belvWYr2qyLVnGL/nl9/055/ed5HwH4F9ARDh4liVKkbVneulkRC08m04Udf+D5U5yRmi5HoD2y274X5BgQdQqHEENRgMpU1h5hqHZDP4pJTEYXcKxyNJnk9E8+GaeGNovj6/O0Ht/qfJONAd+ebmZmFy9ac/1/8Tl3KKCYmylQysgzGml1RsO+xKUdVSG70flTVNKPv0wjahkECOqXhh0xz4ccsfAEUhWfH08AKvGyES0YlIugs2bqvdKjx0eKe6x307uSWYQbKLDcHqoiWI4M8VwdFnPs2ZgWPN/G7773vvlxnQPfQeH+mtrmvvqP7oi9ELL9V1gGWktJzMcIf03pf/h8QYAvAkAa8qkqDVvb1m/llyc+fHILou9taVz5MQ2hjXvYDh6O8PS9Qxn3s5wdD3Dmt9hWBoIoa6ujiJJ1dXVm41G42aDwbABAKoIqQqooqBywyeH9tZ09B0+cNHR1nB++EgDw9ENDGvex7D0bou97XOLvbXv0u8dteeGv3xZWiqVIvIBachrGI3Gsu7d00BkHQC81XK2GToDh6FzpA0ucN/CqcFvwD+zDKeGDt5svLpx07nBEy+76nK5jvI87+V5/hjZm0wm6O7uNkSjUejp6dnqdDpv8H/ww9Yfrm3DLMLP4wOVtkct5Y/vDGlN49MqP3Y33QFgomBiYuJSoVBAAl3X0e/3M7qul6trbGysSiaTj/AVMplMtqurq+arA/vhgYYwFk4fkxIFVBYR5eRf6L63eANUVS0Vi8UVQRD0UqlEkuZIc2w2GwSDwSZRFNHj8eSdTmfO5/NhKBRqfz1+U8rzgDi7iFeu/aoL/pnCbAoRZFmWyeuapulEFUXxkuDe3l5gWXZfLBbDbDaLCwsLK6qqosvl+iISDsITRJicXf5Nij9Ht6joEeUpTin5LFit1p2JRGKemMXj8fmBgYGPBUEgFRiIcTgc7srn81gsFlGSpP5X00SN3XsKv9xSaoMPc9JcGvHuvJ5i/YlPXwAv1yLiTz5IgQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;on_send_def_traversal.png&quot;
        title=&quot;on_send_def_traversal.png&quot;
        src=&quot;/static/31cc05831c7b3e162da8b84f4e3b6440/9f128/on_send_def_traversal.png&quot;
        srcset=&quot;/static/31cc05831c7b3e162da8b84f4e3b6440/12f09/on_send_def_traversal.png 148w,
/static/31cc05831c7b3e162da8b84f4e3b6440/9f128/on_send_def_traversal.png 212w&quot;
        sizes=&quot;(max-width: 212px) 100vw, 212px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Also note that unlike the classical implementation, cops do not implement methods for all node types. Instead, there’s logic that skips non-existent methods during traversal, which is a nice segue to our next section.&lt;/p&gt;
&lt;h2 id=&quot;execution--traversal&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#execution--traversal&quot; aria-label=&quot;execution  traversal permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Execution &amp;#x26; Traversal&lt;/h2&gt;
&lt;p&gt;Object structure and visitors meet in the &lt;a href=&quot;https://github.com/rubocop/rubocop/blob/2fe4b1a6faca23adff13e1bbff6ecf5b66c6447b/lib/rubocop/cop/commissioner.rb#L79-L93&quot;&gt;Rubocop::Commissioner#investigate&lt;/a&gt; method. This method walks the tree and for each node, executes the respective &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; method on all enabled cops.&lt;/p&gt;
&lt;p&gt;The actual implementation is a little convoluted, with a fair amount of metaprogramming. It’s a fun detour, but feel free to skip this! First, my attempt to visualise the flow with a sequence diagram:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 411px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/11a55fd9d5d7db346616d26a674a8543/2a432/rubocop_traversal.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102.7027027027027%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFMklEQVQ4y4VTa0xTZxh+C7UDcbKiTNBoYInZYkI2RjLdmAGZzpmpU1hcNg1xLjHZsvjHEJOxC3PTuhmcv0CjGQ4ichkg9EYrOy13elFoOfTOOT3ntGBLgRYoFYu8y+lKMreZvcmT7z3nPHm+8z3f84LNZgObzfYWTdPIMMwKTdMrHMehx+PRIyIgopCiKLDb7YUURSH/neexLIssy/bHOQJYLY/HA16vd5fX60WO48I0TYf5fnp6epUs9Pl8PG+3x+NBv9+/wLJsmN/U7/dr/y64vLwMUFZWxkNUV1e3VaPRbKmoqNheUvJhZto6SB52LEGA6RYhTiS0NlSlNDbc3qZQKLecO3fupXf379+0J39H8rBlCmbcCuHyTK8wGugWwo0bN2LQ6/XAMAzs27c/eaC//+iYjTni9mFWdNYI+FgPGBoCr0sLI2YrVFdfyzAY9KWaXuP7TmYud8mnBnx0HzCiB6ivr49BLpcn1NbWwvnvv81s75CdcZDdNGMjDBjsOYtzA+UYHjq/4JG+Yh1WwvVrVa9qtD3fjRo7XeNjBINzg+UY6v8KF4bK4Z+1bu2aDN4Xt/mO3KCtQcdwC85yBHpscvTZm48O99XC+pSEGIcZbdTwHOdwKwY9GvTaFRg7Jo/5+XkoKiqC05+WPNdUUwGctcNgNbbgqK4JdZrfkCalGPYqj+DCA7h84WzK9StngB272zOmb0bzYMPiwL1fl0hd01Lsr+I3FevzXns5CReGIMTKP59xd1bPe7sqA5SycmW2v2rZr9iBizo4efxgSsjbA0FW3T/NEDg30bPCr8tBPcJ/1JrfG2oS5qZIYCkz+Lx2oJ0jEA66ATEEuOgSFOTnJnFWOYQ49T3+mKxFOseMScPRmcEwVFdX7zUajXc0Gk2jUqks5hUPHzoIU/7JTXa77QTjpj9yOh0nXC7He3zeHj9BSF2/VoSRdoh6ajbgkmwzPlFm4EpnZtBxPRNycnKyJRLJ9vLy8u0HDhzYmpqa+gJ//OLi4k0ymexrgiDKVCrVN62traVxW8TitLSUmFVLKkAcBEQDIN6HR94WgMLCQrh58yZcvXoVCgoKeF4iSZK7TCbTMYvFUkiSZJHZbN5jtVr3kST5iU6n2wEACROTAfBZbwnmmeanALm5uYLKykpBaWnp6jyura+vP9nX1/ezWq2WqNXqSyqV6pJWq71IEMTltra2d3hSKBQSwP/VqVOnICsrKymWQ7cbdDoduFwusFgsQNM0BAIB8HAsvP3m60lzrEIYoBTCaVoZW2fcncJZViX8l2h2dnYyL9jb27vbaDS2j4yM1JtMphaDwVCt0+lf3Jyx8XneFtrcBO7RlhgoUzOwY23gtbU/LbZz505IT09fFzdfkJeXtyE/P19cVFSUlp2dnbr6vlv2S840Lb/y0Cm94HNKJZOODol/XC4J0ApJTOjxFAGILCwvcrwvCX6/f7fT6TzGsuxBjuMOUxR1yOVyHaKp8eOTs7ht0ta0d8IuRTfZjsxYO1Lmu8hZZTjhUGB8UlzwxWcfwOmPc/lHUWNj05cEQfykVqt/VCgUF7q6unj80NnZeblDKjvMhx+fdGxE/EOMqBYjEmJEpRixXRwXnIyPn0HU1XYxne9nZgIQiUT424TFxcVYHwwG4yMqEGFUA4hmQHwAiKZ4Fgf+8s5NtiU6hltgalx6yz+uQAx1v4EP6yC6QIkikUgiIsYQ7wXCRBAFx28LeISopxETDFAygd8lhxAjK5mmZFWPJqRboj4FPLQ3PStromdl7082QZ4KxBcKcAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;rubocop_traversal.png&quot;
        title=&quot;rubocop_traversal.png&quot;
        src=&quot;/static/11a55fd9d5d7db346616d26a674a8543/2a432/rubocop_traversal.png&quot;
        srcset=&quot;/static/11a55fd9d5d7db346616d26a674a8543/12f09/rubocop_traversal.png 148w,
/static/11a55fd9d5d7db346616d26a674a8543/e4a3f/rubocop_traversal.png 295w,
/static/11a55fd9d5d7db346616d26a674a8543/2a432/rubocop_traversal.png 411w&quot;
        sizes=&quot;(max-width: 411px) 100vw, 411px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when the &lt;code class=&quot;language-text&quot;&gt;Commissioner&lt;/code&gt; class is loaded, it loops through node types, defining “original” &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods via &lt;code class=&quot;language-text&quot;&gt;class_eval&lt;/code&gt; (&lt;a href=&quot;https://github.com/rubocop/rubocop/blob/2fe4b1a6faca23adff13e1bbff6ecf5b66c6447b/lib/rubocop/cop/commissioner.rb#L59-L76&quot;&gt;source&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;#investigate&lt;/code&gt; will execute &lt;a href=&quot;https://github.com/rubocop/rubocop-ast/blob/5b53037d322275cdd5082e4164f976146cc6c014/lib/rubocop/ast/traversal.rb#L17-L22&quot;&gt;#walk&lt;/a&gt; (from an included &lt;code class=&quot;language-text&quot;&gt;Rubocop::AST::Traversal&lt;/code&gt; module) on the root node of the AST, which calls the corresponding “original” &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods defined above.&lt;/li&gt;
&lt;li&gt;the “original” &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods will execute every relevant cop for the node.&lt;/li&gt;
&lt;li&gt;the “original” &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods will also execute &lt;code class=&quot;language-text&quot;&gt;super&lt;/code&gt;, which calls an “inherited” &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; method (again defined in &lt;code class=&quot;language-text&quot;&gt;Rubocop::AST::Traversal&lt;/code&gt; via metaprogramming, see an example for &lt;a href=&quot;https://github.com/rubocop/rubocop-ast/blob/5b53037d322275cdd5082e4164f976146cc6c014/lib/rubocop/ast/traversal.rb#L154-L160&quot;&gt;on_send&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;the “inherited” method loops through child nodes, again invoking the original &lt;code class=&quot;language-text&quot;&gt;on_#{node_type}&lt;/code&gt; methods in Commissioner, this time with the child nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven’t dug up the &lt;em&gt;why&lt;/em&gt; behind this particular design, my guess so far is (1) metaprogramming makes it easier to support new node types, and (2) this was a way to separate the traversal logic from the cop execution logic.&lt;/p&gt;
&lt;p&gt;Note that the classical visitor implementation doesn’t dictate where traversal logic should go. In Rubocop, the main traversal is separate from both object structure and visitors, but in other situations it might work better to put the logic in the object structure (or some parts of it, e.g. composite elements might encapsulate their traversal) or in the visitor itself (e.g. if different visitors need to traverse in a different order)&lt;sup id=&quot;fnref-3&quot;&gt;&lt;a href=&quot;#fn-3&quot; class=&quot;footnote-ref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tradeoffs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tradeoffs&quot; aria-label=&quot;tradeoffs permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;Now that we know how Rubocop implements a visitor-inspired pattern, let’s briefly examine the benefits and cost of the design.&lt;/p&gt;
&lt;h3 id=&quot;benefit-encapsulation-of-each-linting-rule-in-a-visitor&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#benefit-encapsulation-of-each-linting-rule-in-a-visitor&quot; aria-label=&quot;benefit encapsulation of each linting rule in a visitor permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Benefit: Encapsulation of each Linting Rule in a Visitor&lt;/h3&gt;
&lt;p&gt;Each cop encapsulates the linting and formatting logic for a single rule, making it simple to disable or restrict the scope of that cop, without affecting other cops. In Rubocop, you can disable cops via a &lt;a href=&quot;https://docs.rubocop.org/rubocop/1.34/configuration.html#enabled&quot;&gt;small config change&lt;/a&gt;. The traversal logic will then ignore those cops.&lt;/p&gt;
&lt;h3 id=&quot;benefit-ease-of-extension-by-adding-new-visitors&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#benefit-ease-of-extension-by-adding-new-visitors&quot; aria-label=&quot;benefit ease of extension by adding new visitors permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Benefit: Ease of extension by adding new Visitors&lt;/h3&gt;
&lt;p&gt;The object structure and traversal logic is separate from the visitor logic - this makes it easy for a user of the gem (us!) to add and enable new &lt;a href=&quot;https://docs.rubocop.org/rubocop/1.33/development.html&quot;&gt;custom cops&lt;/a&gt;, without needing to touch the object structure or traversal code.&lt;/p&gt;
&lt;h3 id=&quot;cost-coupling-to-object-structure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cost-coupling-to-object-structure&quot; aria-label=&quot;cost coupling to object structure permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cost: Coupling to Object Structure&lt;/h3&gt;
&lt;p&gt;Changes to the underlying object structure may ripple through all visitors. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If node categorisation changes, cops may no longer be checking correctly for those types e.g. in Ruby 3, how pattern matching was parsed &lt;a href=&quot;https://github.com/rubocop/rubocop/pull/9873&quot;&gt;changed slightly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If the hierarchy changes and ordering of nodes change, it could break traversal. We saw this coupling earlier in &lt;code class=&quot;language-text&quot;&gt;DefEndAlignment#on_send&lt;/code&gt;, which expects that &lt;code class=&quot;language-text&quot;&gt;def&lt;/code&gt; nodes are children of &lt;code class=&quot;language-text&quot;&gt;send&lt;/code&gt;. If that changes, the cop wouldn’t work properly. Arguably, this is rare in Ruby’s case, and can also be mitigated through more abstract traversal (e.g. &lt;a href=&quot;https://docs.rubocop.org/rubocop-ast/node_pattern.html&quot;&gt;node pattern&lt;/a&gt;, which reminds me a lot of CSS selectors).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;cost-reduced-encapsulation-of-object-structure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cost-reduced-encapsulation-of-object-structure&quot; aria-label=&quot;cost reduced encapsulation of object structure permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cost: Reduced encapsulation of Object Structure&lt;/h3&gt;
&lt;p&gt;Depending on the needs of visitors, objects in the structure may need to expose more information, potentially breaking encapsulation. In our example, Rubocop cops need to know about positions in the actual source code through methods like &lt;code class=&quot;language-text&quot;&gt;#loc&lt;/code&gt;, which is less useful for a program emulating the Ruby interpreter - it only needs to execute the logic represented by the node types.&lt;/p&gt;
&lt;h2 id=&quot;afterword&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#afterword&quot; aria-label=&quot;afterword permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Afterword&lt;/h2&gt;
&lt;p&gt;I hope this helped concretise when and why you might want to use the visitor pattern. Here are some other examples you may want to look into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;nokogiri&lt;/code&gt;, a XML/HTML parsing library, has a &lt;a href=&quot;https://github.com/sparklemotion/nokogiri/blob/148bdf5edc1f9963274b57ae899b4f651b53315b/lib/nokogiri/css/xpath_visitor.rb&quot;&gt;CSS::XPathVisitor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;syntax-tree&lt;/code&gt;, another Ruby parser, has &lt;a href=&quot;https://github.com/ruby-syntax-tree/syntax_tree/blob/main/lib/syntax_tree/visitor.rb&quot;&gt;visitor support&lt;/a&gt; (see &lt;a href=&quot;https://github.com/ruby-syntax-tree/syntax_tree/pull/40/files&quot;&gt;PR&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;graphql-ruby&lt;/code&gt;, a GraphQL parser, also has an &lt;a href=&quot;https://graphql-ruby.org/language_tools/visitor.html&quot;&gt;AST Visitor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was invited to share about this at RubySG, here’s a video with some cold jokes:
&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; &quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Talk: How Rubocop uses the Visitor Pattern&quot; src=&quot;https://www.youtube.com/embed/psOz2Lgp6KU?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; sandbox=&quot;allow-same-origin allow-scripts allow-popups&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;See &lt;a href=&quot;https://en.m.wikipedia.org/wiki/Lint_(software)&quot;&gt;Wikipedia&lt;/a&gt;.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;See class diagram in Visitor chapter of &lt;a href=&quot;https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8&quot;&gt;Design Patterns book&lt;/a&gt;, or on &lt;a href=&quot;https://refactoring.guru/design-patterns/visitor#structure&quot;&gt;refactoring.guru&lt;/a&gt;.&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;See “Who is responsible for traversing the object structure?” in Visitor chapter,  &lt;a href=&quot;https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8&quot;&gt;Design Patterns book&lt;/a&gt;&lt;a href=&quot;#fnref-3&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Overspecifying Tests - the Employee Report Kata]]></title><description><![CDATA[I paired recently with @andyw8 on the Employee Report kata, which illustrates how tests become more difficult to maintain if they are…]]></description><link>https://www.wasabigeek.com/blog/overspecifying-tests-the-employee-report-kata/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/overspecifying-tests-the-employee-report-kata/</guid><pubDate>Wed, 22 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I paired recently with &lt;a href=&quot;https://twitter.com/andyw8&quot;&gt;@andyw8&lt;/a&gt; on the Employee Report kata&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, which illustrates how tests become more difficult to maintain if they are “overspecified”. For such an unassuming kata, we found a lot to discuss and ponder about. I found his refactoring particularly interesting and wanted to share it.&lt;/p&gt;
&lt;h2 id=&quot;what-is-overspecifying&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-overspecifying&quot; aria-label=&quot;what is overspecifying permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is Overspecifying?&lt;/h2&gt;
&lt;p&gt;In the context of the kata, a test is overspecified when it unintentionally (or intentionally?) tests &lt;em&gt;more&lt;/em&gt; than it should. This couples the test to those additional behaviours, causing the test to fail if those behaviours change.  Walking through the kata gives us some great examples, so let’s learn while doing!&lt;/p&gt;
&lt;h2 id=&quot;the-kata&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-kata&quot; aria-label=&quot;the kata permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Kata&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://codingdojo.org/kata/Employee-Report/&quot;&gt;instructions&lt;/a&gt; are to build a program for a shop owner. The program should generate a report of employees allowed to work on Sundays, given input data like the following:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Max&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Nina&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There are 4 requirements, and you’re encouraged &lt;em&gt;not&lt;/em&gt; to look ahead, instead testing and implementing one requirement at a time. We’ll use Ruby and Minitest in our example.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Suggestion&lt;/strong&gt;: Before going further, you may want to try the kata for yourself - it’s pretty short! When I try katas unprompted and then look through someone else’s solution, I either learn more from their solution or can take a more critical viewpoint.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;first-requirement-18-and-above&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#first-requirement-18-and-above&quot; aria-label=&quot;first requirement 18 and above permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;First Requirement: 18 and above&lt;/h2&gt;
&lt;p&gt;We first want to exclude employees &amp;#x3C;18 years of age. This is our first test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Max&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# exclude&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Nina&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# exclude&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees
  
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;employee&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; employee&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We then write the &lt;a href=&quot;https://github.com/wasabigeek/katas/pull/4/commits/afd4bc1b0dd5eb80d4514d2464959dba2b3da962&quot;&gt;code&lt;/a&gt; to get the test passing ✅. (I’ll mostly link out the implementations because I think it’s secondary to the point of the kata.)&lt;/p&gt;
&lt;h2 id=&quot;second-requirement-sorted-names&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#second-requirement-sorted-names&quot; aria-label=&quot;second requirement sorted names permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Second Requirement: Sorted Names&lt;/h2&gt;
&lt;p&gt;The second requirement is to sort employees by name. We write another failing test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_result_sorted_by_name&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# include only employees that are &gt;18 to keep the test focused.&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees

    &lt;span class=&quot;token comment&quot;&gt;# the only difference from the previous test is the order of the result.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# the requirement wasn&apos;t specific, so we default to alphabetical order. &lt;/span&gt;
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;employee&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; employee&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We do a small &lt;a href=&quot;https://github.com/wasabigeek/katas/pull/4/commits/4ba5627a4dfb328356f56a3d8d704931b4a6e31e&quot;&gt;refactor&lt;/a&gt; before &lt;a href=&quot;https://github.com/wasabigeek/katas/pull/4/commits/195588d660ca67865a39e2703acfd51b6d2c5f51&quot;&gt;addressing the requirement&lt;/a&gt;.  Our new test passes! ✅&lt;/p&gt;
&lt;p&gt;One problem: when we run all the tests, &lt;strong&gt;the first test fails&lt;/strong&gt; ❌. This is in spite of the logic being accurate (our results still excludes employees &amp;#x3C;18 years). It’s clear that in addition to checking that employees were filtered out, our first test &lt;em&gt;also&lt;/em&gt; implicitly tested the ordering.&lt;/p&gt;
&lt;p&gt;RSpec has a matcher called &lt;code class=&quot;language-text&quot;&gt;contain_exactly&lt;/code&gt; that disregards the ordering. Minitest does not, but we could easily &lt;code class=&quot;language-text&quot;&gt;sort&lt;/code&gt; the expected and actual results so that it disregards ordering:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  
    result_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;employee&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; employee&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_result_sorted_by_name&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(EDIT: our previous implementation used &lt;code class=&quot;language-text&quot;&gt;assert&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;refute_includes&lt;/code&gt; to convey the intent that we’re checking only for the presence of certain employees. &lt;a href=&quot;https://twitter.com/deliberatecoder&quot;&gt;@deliberatecoder&lt;/a&gt; correctly pointed out that this would not catch duplicate employees. I think this new implementation still captures the intent of the test, which is that we return only employees &gt;18 years old.)&lt;/p&gt;
&lt;p&gt;Now the first test is decoupled from the ordering of the result, but there is still at least one hidden coupling…&lt;/p&gt;
&lt;h2 id=&quot;third-requirement-all-caps&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#third-requirement-all-caps&quot; aria-label=&quot;third requirement all caps permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Third Requirement: All Caps&lt;/h2&gt;
&lt;p&gt;Now, we are asked to uppercase all the names:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_result_sorted_by_name&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_results_are_uppercased&lt;/span&gt;&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees

    &lt;span class=&quot;token comment&quot;&gt;# OK, you got us, we are potentially overspecifying here too 😝.&lt;/span&gt;
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;MIKE&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SEPP&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;employee&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; employee&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; 
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We naively implement the third requirement with some &lt;a href=&quot;https://github.com/wasabigeek/katas/pull/4/commits/6505cdd7c44f92a0e201a76f406351bf002cb556&quot;&gt;ugly code&lt;/a&gt;. Our new test passes ✅, but guess what? &lt;strong&gt;Our first two tests are now failing&lt;/strong&gt; ❌. Turns out they were also implicitly testing that an employee’s attribute was in a certain format, when all we actually wanted to check was that the employee itself was included (properties VS identity?).&lt;/p&gt;
&lt;h2 id=&quot;optional-refactoring-to-objects&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#optional-refactoring-to-objects&quot; aria-label=&quot;optional refactoring to objects permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Optional: Refactoring to Objects&lt;/h2&gt;
&lt;p&gt;Now we could simply change all the tests to check for capitalized names, but I thought the refactoring @andyw8 proposed was very cool (though you can poke holes in it, see the &lt;a href=&quot;/blog/overspecifying-tests-the-employee-report-kata/#afterword&quot;&gt;afterword&lt;/a&gt;). Could we get the tests to compare the “identity” of the employees, instead of one of their attributes?&lt;/p&gt;
&lt;p&gt;What we did was to introduce a new &lt;code class=&quot;language-text&quot;&gt;Employee&lt;/code&gt; class and use that during comparisons, instead of checking for an attribute e.g.:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# Note the new Employee class.&lt;/span&gt;
      max &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Max&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      sepp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      mike &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees
    &lt;span class=&quot;token comment&quot;&gt;# We&apos;re now comparing the Employee objects,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# instead of their attributes.&lt;/span&gt;
    assert_contains_exactly &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sepp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mike&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_result_sorted_by_name&lt;/span&gt;&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      sepp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      mike &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Mike&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;51&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees
    &lt;span class=&quot;token comment&quot;&gt;# Same here - this test doesn&apos;t rely on the &lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# attributes of an employee!&lt;/span&gt;
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;mike&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sepp&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_results_are_uppercased&lt;/span&gt;&lt;/span&gt;
    employees &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Employee&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Sepp&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;employees&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sunday_allowed_employees
    assert_equal result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;first&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SEPP&apos;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_contains_exactly&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sort&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that some liberties were taken since the requirements gave us a lot of autonomy: we assumed that we had some way to convert the input (an array of hashes) into objects, and that the output could also be a set of objects.&lt;/p&gt;
&lt;p&gt;In the context of the kata, this refactoring is optional because the way the final requirement is written doesn’t require it. But it’s a really good example of how we can encapsulate logic in objects! I did have to take quite a few steps (you can follow the &lt;a href=&quot;https://github.com/wasabigeek/katas/compare/14ab7ffe4a9ce2d780c9dfa7231e5f25f3a40e38...6fac6fb78bbee0aac80ad722ee997db28a45ed24&quot;&gt;commits&lt;/a&gt; here) to “safely” refactor, but the end result is quite satisfying.&lt;/p&gt;
&lt;h2 id=&quot;fourth-requirement-sort-descending&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#fourth-requirement-sort-descending&quot; aria-label=&quot;fourth requirement sort descending permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Fourth Requirement: Sort Descending&lt;/h2&gt;
&lt;p&gt;We now want the names to be in descending order. Since we already have a test for ordering, we can modify that instead of creating a new one:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EmployeeReportTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Test
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_return_employees_older_than_18&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_result_sorted_by_names_descending&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# swap the ordering&lt;/span&gt;
    assert_equal &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sepp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mike&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_results_are_uppercased&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the newly refactored Employee class, we can &lt;a href=&quot;https://github.com/wasabigeek/katas/pull/4/commits/6fac6fb78bbee0aac80ad722ee997db28a45ed24&quot;&gt;modify the comparison method&lt;/a&gt; &lt;code class=&quot;language-text&quot;&gt;&amp;lt;=&gt;&lt;/code&gt; (a.k.a. the “spaceship” operator). This time, none of our other tests fail ✅ - &lt;strong&gt;we have successfully defeated overspecification&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;Or have we?&lt;/p&gt;
&lt;h2 id=&quot;takeaways&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#takeaways&quot; aria-label=&quot;takeaways permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Takeaways&lt;/h2&gt;
&lt;p&gt;I don’t think most of us &lt;em&gt;intentionally&lt;/em&gt; overspecify our tests, so this kata is more a reminder to compare what we’re &lt;em&gt;actually&lt;/em&gt; testing VS what we &lt;em&gt;want&lt;/em&gt; to test. Could we be more specific?&lt;/p&gt;
&lt;p&gt;From the kata, some possible heuristics (we all know that heuristics don’t always work, right?):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when checking for the presence of an object in a result, avoid implicitly comparing the ordering.&lt;/li&gt;
&lt;li&gt;when comparing objects, avoid comparing by their attributes and try to find some way to compare their identity instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;afterword&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#afterword&quot; aria-label=&quot;afterword permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Afterword&lt;/h2&gt;
&lt;p&gt;I had a lot of fun doing this kata, and @andyw8’s refactoring reminded me of why I appreciate object-oriented programming. That said, there are some decisions made in the refactoring that can be debated or improved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Employee#name&lt;/code&gt; always returns the capitalized name. This might not be an assumption we want to apply application-wide (e.g. what if we wanted another report that only capitalizes the initials), and could be considered too pre-emptive.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;Employee#&amp;lt;=&gt;&lt;/code&gt; could be similarly argued, though IMO this seems more broadly applicable across an application as a “default” sort.&lt;/li&gt;
&lt;li&gt;In our tests, we are relying on comparison of the Ruby object ids, meaning that the EmployeeReport class &lt;em&gt;must&lt;/em&gt; return the exact instances that were passed into it. If some step in between created new instances (e.g. for immutability), these tests would fail. (I think this can be easily resolved through adding &lt;code class=&quot;language-text&quot;&gt;Employee#==&lt;/code&gt; though.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the first two points, we won’t truly know if the refactor was “right” or “wrong” until we get new requirements! So, &lt;a href=&quot;https://wasabigeek.com/blog/coupling-and-cohesion-is-a-tradeoff/#refactoring-is-important&quot;&gt;observe future changes, and continue to refactor if necessary&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are also some decisions that could be worth expounding on… another time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;not memoizing the array of employee data, instead choosing to “duplicate” for each test (eventually we purposefully changed the data for each test).&lt;/li&gt;
&lt;li&gt;choosing to have many focused tests, when perhaps one big test might have achieved a similar result.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;What is a kata? The term has &lt;a href=&quot;https://en.m.wikipedia.org/wiki/Kata&quot;&gt;origins&lt;/a&gt; in martial arts, but in software it seems to generally refer to practice exercises. I like the parallel of following and repeating a certain set of movements, but I don’t think everyone subscribes to this specific interpretation.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Coupling is a Tradeoff]]></title><description><![CDATA[In software, coupling is often described as something intrinsic and fixed: code is either coupled or not. I’ve had this viewpoint challenged…]]></description><link>https://www.wasabigeek.com/blog/coupling-and-cohesion-is-a-tradeoff/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/coupling-and-cohesion-is-a-tradeoff/</guid><pubDate>Wed, 08 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In software, coupling is often described as something intrinsic and fixed: code is either coupled or not. I’ve had this viewpoint challenged recently during a talk by Kent Beck, where he makes this statement (&lt;a href=&quot;https://youtu.be/3gib0hKYjB0?t=840&quot;&gt;~14:00&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Coupling is always with respect to a particular change&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This formulation suggests we stop treating coupling as something that is strictly intrinsic to our code. Rather, we consciously (or sub-consciously) make coupling tradeoffs when choosing how to modularize our code.&lt;/p&gt;
&lt;p&gt;What does this mean for us? I had these takeaways from the talk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not all coupling is obvious from the code - a new change may reveal hidden coupling. Thus, observation should be an important part of developing new features.&lt;/li&gt;
&lt;li&gt;Coupling is difficult to completely eliminate from a large system, YAGNI&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; is a good perspective when making coupling tradeoffs.&lt;/li&gt;
&lt;li&gt;Refactoring is an important part of the toolbox - it helps us change our coupling tradeoffs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before we look at each point in detail, let’s align on our understanding of coupling.&lt;/p&gt;
&lt;h2 id=&quot;what-is-coupling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-coupling&quot; aria-label=&quot;what is coupling permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is Coupling?&lt;/h2&gt;
&lt;p&gt;Beck gives a pretty simple definition of coupling: given two software “elements” (this could be at multiple levels, from code modules to web services) and a requested change, the elements are coupled if both need to be modified to accomodate the change.&lt;/p&gt;
&lt;p&gt;I like how this hints at &lt;em&gt;why&lt;/em&gt; decoupling is important - if we need to modify many elements to effect a change, that change is likely to be more costly. So a key advantage of decoupling is potentially reducing future cost.&lt;/p&gt;
&lt;h2 id=&quot;an-example-extracting-twitters-algorithm&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#an-example-extracting-twitters-algorithm&quot; aria-label=&quot;an example extracting twitters algorithm permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;An Example: Extracting Twitter’s “Algorithm”&lt;/h2&gt;
&lt;p&gt;In a &lt;a href=&quot;https://blog.bethcodes.com/coupling-and-cohesion-how-musk-would-need-to-rearchitect-twitter&quot;&gt;blog post&lt;/a&gt;, Beth Andres-Beck shares a great example of how coupling varies depending on the change required. Recently, Elon Musk mused about extracting and open-sourcing “Twitter’s algorithm”, and this is apparently no small feat. Beth observes that the difficulty is likely due to “the algorithm” being spread across multiple microservices.&lt;/p&gt;
&lt;p&gt;Note that this doesn’t mean that Twitter’s services are poorly designed - in fact, Beth argues:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With respect to different changes, Twitter’s system is already &lt;strong&gt;highly cohesive&lt;/strong&gt; and &lt;strong&gt;loosely coupled&lt;/strong&gt;. Twitter grew revenue 16% yoy last quarter. It has recently made obvious strides in reducing harassment &amp;#x26; abuse on the platform. All of that involved hundreds of engineers safely evolving running software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To illustrate, let’s assume multiple microservices are involved in determining a user’s feed:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 441px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4a92d4bf9cf17c40696bf6989ec0f3a7/efc6e/coupling_tradeoff.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 18.243243243243242%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA4klEQVQY0y2OzQ5GMBBF+wB23wuw9YpehcTT2BKJ1M9aixbTFhup+TJiN5mTe+5l67piVVXYNM0zTRNaa32e5xEisnme9TzPxH1d1564MQbiOA6KovgppW4hBJZl+XDOkVwMAJBCUsqHbuecz7IsYoyxZVn0vu84TZOXUnpjzCsMwzAAgN+yLDdxylKZcw7Ztm3Yti12Xfd8AZ8kySscx1FrrZFz7vu+98dx0ApgjAXnef6klLdSivgzDANe14WMJCQFgIcaAMCnafoKhRD6457+xLdtgyiKAmvtK/z4O8Zai389lgTFTZL2qgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;coupling_tradeoff.png&quot;
        title=&quot;coupling_tradeoff.png&quot;
        src=&quot;/static/4a92d4bf9cf17c40696bf6989ec0f3a7/efc6e/coupling_tradeoff.png&quot;
        srcset=&quot;/static/4a92d4bf9cf17c40696bf6989ec0f3a7/12f09/coupling_tradeoff.png 148w,
/static/4a92d4bf9cf17c40696bf6989ec0f3a7/e4a3f/coupling_tradeoff.png 295w,
/static/4a92d4bf9cf17c40696bf6989ec0f3a7/efc6e/coupling_tradeoff.png 441w&quot;
        sizes=&quot;(max-width: 441px) 100vw, 441px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In daily work, a dev working on Service A would have been able to make modifications without touching other services. From that perspective, the services were loosely coupled.&lt;/p&gt;
&lt;p&gt;However, to extract out “the algorithm”, all the services would need to be modified i.e. from this perspective, they are coupled.&lt;/p&gt;
&lt;h2 id=&quot;not-all-coupling-is-discernible-from-the-code&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#not-all-coupling-is-discernible-from-the-code&quot; aria-label=&quot;not all coupling is discernible from the code permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Not all coupling is discernible from the code&lt;/h2&gt;
&lt;p&gt;In Kent Beck’s talk, he also mentions that the most expensive couplings are the ones “you don’t see”. His &lt;a href=&quot;https://youtu.be/3gib0hKYjB0?t=1473&quot;&gt;example&lt;/a&gt; is somewhat extreme: a change in the backup schedule of an application blocked connections and starved other applications on the same server rack.&lt;/p&gt;
&lt;p&gt;John Ousterhout&lt;sup id=&quot;fnref-2&quot;&gt;&lt;a href=&quot;#fn-2&quot; class=&quot;footnote-ref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; gives a more relatable example - changing the background colours in a website. If the colours were defined in an inline style on each page, a developer would need to modify all pages. Yet, this coupling (Ousterhout refers to this as “complexity”) isn’t obvious from characteristics of the code itself - there aren’t any shared dependencies.&lt;/p&gt;
&lt;p&gt;My takeaway? When there are new requirements or changes needed, observe which code needs to be modified. Do you need to modify code in multiple modules or services? That is a sign that they are coupled with respect to that change, and may need to be refactored to make those modifications easier. In the background colour example, this might mean some central variable to control the colour (you could argue this introduces a coupling to that central stylesheet).&lt;/p&gt;
&lt;h2 id=&quot;coupling-is-difficult-to-completely-eliminate-from-a-large-system&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#coupling-is-difficult-to-completely-eliminate-from-a-large-system&quot; aria-label=&quot;coupling is difficult to completely eliminate from a large system permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Coupling is difficult to completely eliminate from a large system&lt;/h2&gt;
&lt;p&gt;Following on from the server rack example, Kent Beck emphasises that the bigger and more complex the system, the more likely you’d find “weird, action at a distance coupling”.&lt;/p&gt;
&lt;p&gt;Even if we break our logic into small, composable parts, those parts need to be combined and orchestrated. This gets repeated at multiple levels in software (from modules to services etc.), creating coupling based on how those parts are composed.&lt;/p&gt;
&lt;p&gt;The Twitter example shows that a choice was made to decouple a certain “axis”, spreading the generation of a user’s feed across multiple services. This allowed the company to iterate and evolve quickly, with the tradeoff that it was difficult to extract and isolate the algorithm as a whole.&lt;/p&gt;
&lt;p&gt;So perhaps it’s worth thinking of coupling as a tradeoff, rather than a fixed property. We decide certain changes are more important to enable today and decouple along those axes, knowing the result could still be coupled from certain perspectives.&lt;/p&gt;
&lt;h2 id=&quot;refactoring-is-important&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#refactoring-is-important&quot; aria-label=&quot;refactoring is important permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Refactoring is important&lt;/h2&gt;
&lt;p&gt;Earlier, I chose to emphasise observation of changes over speculation, because it’s very easy to get it wrong and cause more harm than good. It’s more important to be able to adapt, and that’s why it’s important to get good at refactoring. To quote Kent Beck from &lt;a href=&quot;https://tidyfirst.substack.com/p/tldr-coupling-and-later-cohesion/comment/4965495?s=r&quot;&gt;elsewhere&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You might be able to guess that I care less about predicting the future than in doing a good job with the present and adapting flexibly to learning and surprises.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ll lean on Beth Andres-Beck’s example again, where he too invokes YAGNI (you ain’t gonna need it):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine that Twitter had guessed that a billionaire would get mad about an ad they showed him. They might have spent a similar amount of time &amp;#x26; effort as this project will take today making the ad targeting logic cohesive &amp;#x26; decoupled. The code still wouldn’t be any more cohesive or loosely coupled with regards to the change that [Elon Musk] actually wants. It would have cost a bunch of money to do, making all the other work over the years harder, and it still wouldn’t make this change any easier.&lt;/p&gt;
&lt;p&gt;Attempting to anticipate the future doesn’t help us build systems that can adapt to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, prioritise getting good at (1) observing the system to identify coupling and (2) knowing how to refactor safely, rather than getting better at speculating future requirements. Refactoring should be seen as an essential part of our toolbox for attaining “low coupling and high cohesion”.&lt;/p&gt;
&lt;p&gt;(Note that the above refers to &lt;em&gt;guessing future requirements&lt;/em&gt;, as opposed to the requirements themselves being speculative. I think the latter is somewhat true most of the time - you don’t truly know if a feature is going to be a success until it’s out in the wild.)&lt;/p&gt;
&lt;h2 id=&quot;afterword&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#afterword&quot; aria-label=&quot;afterword permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Afterword&lt;/h2&gt;
&lt;p&gt;I only touched on coupling in this post, but Kent Beck also talks about cohesion in his talk - it’s a good watch, though you may want to skip the first 9 minutes of banter. Here’s a &lt;a href=&quot;https://youtu.be/3gib0hKYjB0?t=545&quot;&gt;good timestamp&lt;/a&gt; to start from.&lt;/p&gt;
&lt;p&gt;Personally, I hope to revisit this post with more concrete, personal examples. Also, few things are truly black-and-white in software design - let’s see if I still agree with these takeaways or develop more nuance as time progresses.&lt;/p&gt;
&lt;p&gt;In the meantime, I’d love to hear your thoughts and discuss further - feel free to tweet or leave a comment!&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;You Aren’t Gonna Need It, an acronym coined by Kent Beck. &lt;a href=&quot;https://martinfowler.com/bliki/Yagni.html&quot;&gt;Here’s&lt;/a&gt; a detailed explanation by Martin Fowler.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;A Philosophy of Software Design, Chapter 2.3&lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Zettelkasten from "How to take Smart Notes"]]></title><description><![CDATA[If you’re trying to get more productive at writing or content creation (especially long-form), I highly recommend checking out How to take…]]></description><link>https://www.wasabigeek.com/blog/zettelkasten-and-how-to-take-smart-notes/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/zettelkasten-and-how-to-take-smart-notes/</guid><pubDate>Sat, 16 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’re trying to get more productive at writing or content creation (especially long-form), I highly recommend checking out &lt;a href=&quot;https://takesmartnotes.com/&quot;&gt;How to take Smart Notes&lt;/a&gt;. It’s a book by Soenke Ahrens which describes “Zettelkasten”, a system of note-taking and personal knowledge management geared towards writing. Here’s a review and summary of the book, which I think would be more accurately titled &lt;em&gt;&lt;strong&gt;“Note-taking to generate ideas for Writing”&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;key-takeaway-purposeful-note-taking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#key-takeaway-purposeful-note-taking&quot; aria-label=&quot;key takeaway purposeful note taking permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Key Takeaway: Purposeful Note-taking&lt;/h2&gt;
&lt;p&gt;To date, I’d been focused on the mechanics of Zettelkasten without understanding it’s goals and philosophies. My &lt;a href=&quot;https://wasabigeek.com/blog/my-learning-workflow-for-2022/&quot;&gt;note-taking&lt;/a&gt; was an attempt to make it easier to find and retain information - in effect, creating a personal wiki. So this passage from the book caught my eye:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…it would be rather misleading to think of his slip-box as a personal Wikipedia or a database on paper. The similarities are obviously there, but the subtle differences are what makes this system unique.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note-taking, in Ahren’s opinion, should be &lt;strong&gt;more purposeful&lt;/strong&gt;. In a Zettelkasten system, the resultant notes should lead to publishable content:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of focusing on the in-between steps and trying to make a science out of underlining systems, reading techniques or excerpt writing, everything is streamlined towards one thing only: insight that can be published.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eventually, you should be able to get a “critical mass” of notes, such that they become an “idea generator” for your writing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is one reliable sign if you managed to structure your workflow according to the fact that writing is not a linear process, but a circular one: the problem of finding a topic is replaced by the problem of having too many topics to write about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How can our notes achieve this? I like to think of it as building an “idea bank” 🏦, where notes are already processed, potential ideas for future writing. They should also be connected to other relevant notes - this allows us to follow the chain of notes to find related ideas (I like to imagine this as creating literal “trains of thought” 🚂).&lt;/p&gt;
&lt;p&gt;I’ve attempted to summarise the process below. Important: The process, while a little abstract, is surprisingly simple. The catch is it depends a lot on you actually processing what you read e.g. rephrasing to develop understanding, connecting the ideas to existing ideas.&lt;/p&gt;
&lt;h2 id=&quot;summarising-zettelkasten&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#summarising-zettelkasten&quot; aria-label=&quot;summarising zettelkasten permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Summarising Zettelkasten&lt;/h2&gt;
&lt;p&gt;“Zettelkasten” means “slip-box” in German. This is in reference to the physical approach of writing on index cards and filing them in a box. Despite the physical roots, it’s still very applicable in today’s digital environment, and can be summarised as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take “Fleeting Notes” or “Literature Notes”&lt;/li&gt;
&lt;li&gt;Write “Permanent Notes”&lt;/li&gt;
&lt;li&gt;Connect and Index your Permanent Notes&lt;/li&gt;
&lt;li&gt;Expand upon ideas in your Permanent Notes&lt;/li&gt;
&lt;li&gt;Write from your ideas&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;1a-take-fleeting-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1a-take-fleeting-notes&quot; aria-label=&quot;1a take fleeting notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1a. Take Fleeting Notes&lt;/h3&gt;
&lt;p&gt;These are effectively shower thoughts 🚿, ideas that come to you in random places. Have a way to quickly write them down, and don’t overthink it - they’ll get discarded once processed into Permanent Notes (Step 2) anyway. Note: this isn’t a rigid process - if a thought is already well-formed, it can become a Permanent Note straightaway.&lt;/p&gt;
&lt;h3 id=&quot;1b-take-literature-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1b-take-literature-notes&quot; aria-label=&quot;1b take literature notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1b. Take Literature Notes&lt;/h3&gt;
&lt;p&gt;These are notes that you take when reading something, the primary goal being to develop &lt;em&gt;understanding&lt;/em&gt; of the text, which will help later in the process. Avoid copying directly - rephrasing helps you develop and check your understanding. Although Ahrens says you are free to use any note-taking methodology or technique, he also suggests to be picky. Since the purpose isn’t to remember everything (you can add a bibliographical reference for that), ideally the notes taken should be concise and relevant to you.&lt;/p&gt;
&lt;p&gt;For example, Ahrens read a book about scarcity and how people seem to react irrationally to it (e.g. people with little money spend unnecessarily on luxuries like take-away food):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I took some literature notes collecting reasons how and why humans act so very differently when they experience scarcity. This was step one, done with an eye towards the argument of the book. I had questions in mind like: Is this convincing? What methods do they use? Which of the references are familiar?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Above, we can see that his focus was on understanding the book’s argument (the reasons why humans act differently). The questions at this point are narrower, focused on the text itself.&lt;/p&gt;
&lt;h3 id=&quot;2-write-permanent-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-write-permanent-notes&quot; aria-label=&quot;2 write permanent notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. Write Permanent Notes&lt;/h3&gt;
&lt;p&gt;Here we begin to process the notes in Step 1, converting them into “permanent notes”. This involves thinking about how they relate to your research and interests (which comparing to existing Permanent Notes can help with):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The idea is not to collect, but to develop ideas, arguments and discussions. Does the new information contradict, correct, support or add to what you already have (in the slip-box or on your mind)? Can you combine ideas to generate something new? What questions are triggered by them?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is perhaps the most abstract step of the process - how do you know what is worth keeping, especially in the beginning when you have few notes to compare with? Though he doesn’t explicitly say this, Ahrens talks about “what interests you” a lot, and I think that’s a decent heuristic - write about what you find “interesting”. If it interests you, there’s a higher chance you’d want to expand on it in the future.&lt;/p&gt;
&lt;p&gt;Another suggested approach for writing Permanent Notes is to write about &lt;em&gt;why something seemed important&lt;/em&gt; to you. Following on the previous Literature Note example in Step 1b - since Ahrens was interested in “a theory of society” (🤷‍♂️), his Permanent Note was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Any comprehensive analysis of social inequality must include the cognitive effects of scarcity. Cf. Mullainathan and Shafir 2013.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Still pretty vague? Unfortunately, yes (at least to me). There is a further example in the book’s appendix which I’ll attempt to break down in a future post.&lt;/p&gt;
&lt;h3 id=&quot;3-connect-and-index-permanent-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-connect-and-index-permanent-notes&quot; aria-label=&quot;3 connect and index permanent notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Connect and Index Permanent Notes&lt;/h3&gt;
&lt;p&gt;The next step is to file/store your Permanent Notes, making sure to add links to and/or from other relevant notes. This will help us find related ideas later when writing.&lt;/p&gt;
&lt;p&gt;To further aid discoverability, keep an “index” of topics or keywords, and make sure the new Permanent Note can be reached from the index. This can be done by adding a link to the note directly next to the topic, or linking it from another note that can be reached through the index.&lt;/p&gt;
&lt;p&gt;The above index might seem irrelevant given the searching functionality in digital solutions, but Ahrens suggests our keywords be more situational instead of purely for categorisation (“In which circumstances will I want to stumble upon this note, even if I forget about it?”). His example was the following note:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Tversky/Kahneman (1973) showed in an experiment that people are more likely to overestimate the likelihood of an event to happen if they are able to conceive it well and in detail than if it were abstract.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we took a categorisational approach, the keyword could be “misjudgements” or “experimental psychology”, which he doubts could be turned into a structured argument. Instead, we should look through existing notes and think about trains of thought and questions.&lt;/p&gt;
&lt;p&gt;If the writer was an economist working on decision-making, he or she might be thinking about how management prefers projects with easily-visible outcomes, even if they’re less profitable. A good keyword could then be “capital allocation problems”.&lt;/p&gt;
&lt;p&gt;As a topic develops, you may want to add an “Overview Note” - a note which starts to structure thoughts and links to relevant Permanent Notes. Unfortunately the book doesn’t give a clearer example of this, though Ahrens says it could be an in-between step to the development of a manuscript (e.g. laying out the arguments for the topic).&lt;/p&gt;
&lt;h3 id=&quot;4-develop-ideas-in-permanent-notes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-develop-ideas-in-permanent-notes&quot; aria-label=&quot;4 develop ideas in permanent notes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4. Develop ideas in Permanent Notes&lt;/h3&gt;
&lt;p&gt;Ahrens talks about developing topics, questions and projects “bottom up” from the system. To my understanding, this is more about mindset than a mechanical difference from Step 3 - instead of starting “top down” like brainstorming topics to write about, look into your notes and expand upon what looks promising (e.g. where clusters of notes form).&lt;/p&gt;
&lt;p&gt;What you end up writing about may be very different from what originally interested you! I love his example about how DNA’s structure was discovered - the original research project was to find a treatment for cancer. As they researched, the scientists were able to pivot to the most promising path.&lt;/p&gt;
&lt;h3 id=&quot;5-write-from-your-ideas&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#5-write-from-your-ideas&quot; aria-label=&quot;5 write from your ideas permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;5. Write from your ideas&lt;/h3&gt;
&lt;p&gt;When an topic is developed far enough, you can copy notes to an outliner and re-order them. This helps you identify what’s missing for further research and note-taking, as well as remove redundant information. At this stage, it’s also easy to try out different ideas and structures for articles. Then… write!&lt;/p&gt;
&lt;h2 id=&quot;the-review&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-review&quot; aria-label=&quot;the review permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Review&lt;/h2&gt;
&lt;p&gt;✅ This book gets the wasabi stamp of approval, especially since I’m trying to get better at writing. If the summary above intrigued you, please give the book a read! There’s a &lt;em&gt;lot&lt;/em&gt; of detail and nuance that I didn’t cover (and honestly, still trying to process how it applies to me). One point worth re-emphasis - the system summarised above is just a skeleton. &lt;strong&gt;Processing what you read and thinking about how it connects is what’s actually important&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here’s some other thoughts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time Commitment&lt;/strong&gt;: Low. Amazon says that this book takes just above 4 hours to read. Personally, I took longer for a first read-through, but was still able to finish it with a few days of intermittent reading (probably &amp;#x3C; 10 hours).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Depth&lt;/strong&gt;: I think the content is pretty dense, in a good way. There’s a lot to ponder about. I like how &lt;a href=&quot;https://fortelabs.co/blog/how-to-take-smart-notes/&quot;&gt;Tiago Forte puts it&lt;/a&gt;: “Every paragraph has a point, and I struggled to leave anything out of this summary.” However, I did feel that the book was more abstract and spoke mostly in terms of principles. I could have used more concrete examples to visualise and understand the process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writing Style&lt;/strong&gt;: You can see the influence of academia in citations and references to other papers, but I thought overall the writing was simple to read. The words weren’t unnecessarily flowerly or complex.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Why use Polymorphism in OOP?]]></title><description><![CDATA[When I first came across the concept of Polymorphism in Object-Oriented Programming (OOP), it wasn’t obvious to me why it was important. Let…]]></description><link>https://www.wasabigeek.com/blog/why-is-polymorphism-important/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/why-is-polymorphism-important/</guid><pubDate>Sun, 20 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I first came across the concept of Polymorphism in Object-Oriented Programming (OOP), it wasn’t obvious to me &lt;em&gt;why&lt;/em&gt; it was important.&lt;/p&gt;
&lt;p&gt;Let’s start with a hot take by &lt;a href=&quot;https://blog.cleancoder.com/uncle-bob/2018/04/13/FPvsOO.html&quot;&gt;Robert C. Martin&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…the thing that truly differentiates OO programs from non-OO programs is polymorphism.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s a bit of a blanket statement (Martin states that this was purposefully reductive), but I do agree that polymorphism is a central aspect of OOP. Let’s dig deeper!&lt;/p&gt;
&lt;h2 id=&quot;what-is-polymorphism&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-polymorphism&quot; aria-label=&quot;what is polymorphism permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is polymorphism?&lt;/h2&gt;
&lt;p&gt;Since polymorphism has different meanings depending on context&lt;sup id=&quot;fnref-1&quot;&gt;&lt;a href=&quot;#fn-1&quot; class=&quot;footnote-ref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, let’s align on what I meant by “Polymorphism in OOP”. In the earlier blog post, Martin explains it as different objects being able to accept the same message, implementing their own behaviour. I’ll paraphase his example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;some_object.do_something()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We don’t actually know what &lt;code class=&quot;language-text&quot;&gt;some_object&lt;/code&gt; is! Nor does it actually matter. Many different implementations could replace &lt;code class=&quot;language-text&quot;&gt;some_object&lt;/code&gt;, and so long as they have the same interface (i.e. have the method &lt;code class=&quot;language-text&quot;&gt;#do_something&lt;/code&gt;), the program would still run.&lt;/p&gt;
&lt;h2 id=&quot;why-use-polymorphism&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-use-polymorphism&quot; aria-label=&quot;why use polymorphism permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why use polymorphism?&lt;/h2&gt;
&lt;p&gt;In another &lt;a href=&quot;https://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html&quot;&gt;blog post&lt;/a&gt;, Martin says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There really is only one benefit to Polymorphism; but it’s a big one. It is the inversion of source code and run time dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It wasn’t initially obvious to me what that meant, so let’s concretise this with an example: caching in a web framework like Rails. There are multiple options out there for caches - for example, Rails supports a &lt;code class=&quot;language-text&quot;&gt;FileStore&lt;/code&gt;, a &lt;code class=&quot;language-text&quot;&gt;MemCacheStore&lt;/code&gt; and a &lt;code class=&quot;language-text&quot;&gt;RedisCacheStore&lt;/code&gt;. For the sake of illustration, imagine that the caches all had their own interfaces:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;example_method&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# do stuff&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;is_a&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; NaiveFileStore
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search_for&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;elsif&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;is_a&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; NaiveRedisCacheStore
    data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# do more stuff&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Above, our &lt;code class=&quot;language-text&quot;&gt;example_method&lt;/code&gt; has a direct dependency on &lt;code class=&quot;language-text&quot;&gt;NaiveFileStore&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;NaiveRedisCacheStore&lt;/code&gt;. If their interfaces change, or new kinds of store need to be supported, it will require changes in &lt;code class=&quot;language-text&quot;&gt;example_method&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Earlier, Martin talked about an inversion of dependencies (the “D” in SOLID) - practically, this means &lt;code class=&quot;language-text&quot;&gt;example_method&lt;/code&gt; and the different cache stores agree on an interface, and both depend on it. This makes all cache stores polymorphic! In Rails, all &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store&quot;&gt;cache stores&lt;/a&gt; implement a &lt;code class=&quot;language-text&quot;&gt;#fetch&lt;/code&gt; method, so the above code becomes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;example_method&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# do stuff&lt;/span&gt;
  data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fetch&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# do more stuff&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code in &lt;code class=&quot;language-text&quot;&gt;example_method&lt;/code&gt; is more concise now, but that’s a side benefit; what’s important is it’s shielded from knowing which cache store is used, and how the store works. It just knows that &lt;em&gt;some&lt;/em&gt; cache store exists (&lt;code class=&quot;language-text&quot;&gt;Rails.cache&lt;/code&gt;), and the cache store has agreed to implement a &lt;code class=&quot;language-text&quot;&gt;#fetch&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;We can visualise this “inversion” below - the arrows can be thought of as “dependency directions”:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 501px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a99ebb7855eee46e02e449256eb4b2af/55811/polymorphic.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 78.37837837837837%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwklEQVQ4y42UiaoqQQxE+/+/wj9RxF0QBUVEwQX3fd+1LieXXAZxHi8QMtNLdSWp7pDJZIQXi0Vls1m1220tFguNx2NNJhO932/F2be5cLlcdL/f9Xw+LZ5OJx0OB4ur1Ur7/V6scWf8eDz+gX2ChsfjIRwwoi9i83Q61fV61XA4VK/XU7/ft39AIfAV8Hw+2wJnttlszAHnn3lY7nY7bbdbA4Th6/X6DtjtdjUajTQYDLRcLm3j7XazSVhwECDuHIDH1TFwGkxwmDgbUqY5pMuBNIlIo6htbA0ZiDpGOrCczWYGQJzP5xY5hNRjU9Y/jDrC1NPlECJjn0Tcg0sh2pxojT5158ziLKTTaeXzeVWrVeVyORN3qVSyVJEN9SJdvqNS+caaGNbrtTWC5nhTvDGAuVyQktcLzbIGMPaREXPlcvm3htFmuH2mxhqXEeB+wzqdjnUfSyaTCtwA1yFdhDHMaEiz2VQqlVKtVrMNzMMWYEBh5xcClvV6/Tdlvx1MkCKR8UajYY9Gq9UyMIRPqrB3QNcwgBAI1MELyqAX3m+EF5zN/nj4tx9CA8mIZob/fZaiBkuUUSgUTBl4pVJRIpHQD9CCzg1WqwTwAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;polymorphic.png&quot;
        title=&quot;polymorphic.png&quot;
        src=&quot;/static/a99ebb7855eee46e02e449256eb4b2af/55811/polymorphic.png&quot;
        srcset=&quot;/static/a99ebb7855eee46e02e449256eb4b2af/12f09/polymorphic.png 148w,
/static/a99ebb7855eee46e02e449256eb4b2af/e4a3f/polymorphic.png 295w,
/static/a99ebb7855eee46e02e449256eb4b2af/55811/polymorphic.png 501w&quot;
        sizes=&quot;(max-width: 501px) 100vw, 501px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Martin describes this as a “plugin architecture”:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This inversion allows the called module to act like a plugin. Indeed, this is how all plugins work…
Plugin architectures are very robust because stable high value business rules can be kept from depending upon volatile low value modules such as user interfaces and databases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With the new setup, the polymorphic cache stores can be swapped for each other without changing the code in &lt;code class=&quot;language-text&quot;&gt;example_method&lt;/code&gt;. New kinds of cache stores can also be supported by creating classes that implement &lt;code class=&quot;language-text&quot;&gt;#fetch&lt;/code&gt; and other methods as specified by &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store&quot;&gt;ActiveSupport::Cache::Store&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In short, polymorphism makes it easier to extend or change aspects of our programs, without a rippling of changes throughout the entire program. (How do we determine the “aspects” to split our programs by? A good read would be &lt;a href=&quot;https://www.wasabigeek.com/blog/what-does-a-1972-paper-have-to-do-with-the-single-responsibility-principle/&quot;&gt;Parnas’ classic 1972 paper&lt;/a&gt;.)&lt;/p&gt;
&lt;h2 id=&quot;afterword&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#afterword&quot; aria-label=&quot;afterword permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Afterword&lt;/h2&gt;
&lt;p&gt;While our cache example does rely on inheritance to make the polymorphic group explicit, inheritance is not a prerequisite. For example, each cache could be a completely unrelated class, and it still would work in Ruby as long as they had a &lt;code class=&quot;language-text&quot;&gt;#fetch&lt;/code&gt; method (this is also called &lt;a href=&quot;https://en.wikipedia.org/wiki/Subtyping#Relationship_with_inheritance&quot;&gt;“duck-typing”&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You may have spotted that Martin talks about inversion of “source code” in addition to runtime dependencies. I understand this has greater implications in compiled programs, but am not familiar enough to expound on it. Do leave a comment if you have an example!&lt;/p&gt;
&lt;p&gt;There are many other neat examples relating to polymorphism. Here’s some I thought of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Martin Fowler’s &lt;a href=&quot;https://martinfowler.com/books/refactoring.html&quot;&gt;refactoring&lt;/a&gt; book has a “Replace Conditional with Polymorphism” refactoring, which I think was neatly illustrated in this &lt;a href=&quot;https://youtu.be/8bZh5LMaSmE&quot;&gt;Sandi Metz talk&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Many &lt;a href=&quot;https://refactoring.guru/design-patterns/behavioral-patterns&quot;&gt;Behavioural Design Patterns&lt;/a&gt; rely on polymorphism, for example the Strategy, Command, Visitor etc.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://refactoring.guru/introduce-null-object&quot;&gt;NullObject&lt;/a&gt; is a neat way to support a null value or no-ops when your code relies on a Polymorphic interface. (Note that while Rails has a &lt;a href=&quot;https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-nullstore&quot;&gt;NullCache&lt;/a&gt;, it doesn’t solve the same issue that the pattern is meant to.)&lt;/li&gt;
&lt;li&gt;Ruby-specific example of a “Plugin Architecture”: Rubocop allows you to add your own &lt;a href=&quot;https://docs.rubocop.org/rubocop/1.26/extensions.html#writing-your-own-cops&quot;&gt;custom cops&lt;/a&gt; without changing the library’s source code, because all cops are polymorphic!&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;footnotes&quot;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;The definition I used for polymorphism is actually somewhat narrow, but seemed like a relatively common understanding in the context of OOP. The &lt;a href=&quot;https://en.m.wikipedia.org/wiki/Polymorphism_(computer_science)&quot;&gt;Wikipedia article&lt;/a&gt; on polymorphism shows a lot more breadth in the topic, and I think what is described above is known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_dispatch&quot;&gt;single, dynamic dispatch&lt;/a&gt;. There’s some really good discussion on this in the &lt;a href=&quot;https://www.reddit.com/r/ruby/comments/tooyby/why_use_polymorphism_in_oop_blog_post/&quot;&gt;Reddit thread&lt;/a&gt; for this post.&lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[A 1972 paper and the Single Responsibility Principle]]></title><description><![CDATA[”A class should only have one reason to change” is a mantra that Object-Oriented advocates have chanted for years. Dubbed the “Single…]]></description><link>https://www.wasabigeek.com/blog/what-does-a-1972-paper-have-to-do-with-the-single-responsibility-principle/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/what-does-a-1972-paper-have-to-do-with-the-single-responsibility-principle/</guid><pubDate>Mon, 28 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;”&lt;em&gt;A class should only have one reason to change&lt;/em&gt;” is a mantra that Object-Oriented advocates have chanted for years. Dubbed the “Single Responsibility Principle” (SRP), it remains somewhat abstract till this day ✨. Abstract enough, in fact, that it’s originator (Robert Martin) felt it worthwhile to explain again in a &lt;a href=&quot;https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html&quot;&gt;blog post&lt;/a&gt; - 14 years after he first wrote about it!&lt;/p&gt;
&lt;p&gt;That blog post begins by referencing and quoting a 1972 paper, ”&lt;strong&gt;On the Criteria To Be Used in Decomposing Systems into Modules&lt;/strong&gt;” by David Parnas:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We have tried to demonstrate by these examples that it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart.&lt;/p&gt;
&lt;p&gt;We propose instead that one begins with a list of difficult design decisions or design decisions &lt;em&gt;which are likely to change&lt;/em&gt;. Each module is then designed to hide such a decision from the others.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The paper seemed significant, as Martin wrote that the SRP appeared “to align with Parnas’ formulation”. Could it demystify the SRP? What exactly was this paper about?&lt;/p&gt;
&lt;p&gt;(Note: I’ve tried &lt;em&gt;not&lt;/em&gt; to abstract too much of the paper, in the hope that you’d be able to draw your own conclusions as well.)&lt;/p&gt;
&lt;h2 id=&quot;the-system-kwac-index&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-system-kwac-index&quot; aria-label=&quot;the system kwac index permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The System: KWAC Index&lt;/h2&gt;
&lt;p&gt;First, Parnas sets the stage - he would compare two criteria for modularizing a system, showing that one provided superior flexibility. The system in question was a KWAC (KeyWord Alongside Context) index generator.&lt;/p&gt;
&lt;p&gt;KWAC was an indexing system for technical manuals, allowing a reader to quickly find where in the manual a keyword was used. In addition to showing the keyword, KWAC would show the rest of the sentence as well, it’s “context”:
&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/6/61/KWAC.png&quot; alt=&quot;Sample KWAC Index from Wikipedia&quot;&gt;&lt;/p&gt;
&lt;p&gt;To build the KWAC index, the system would take in file of sentences. Say the input file looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# cat input.txt&lt;/span&gt;
Program Development &lt;span class=&quot;token comment&quot;&gt;# line 1&lt;/span&gt;
On The Criteria &lt;span class=&quot;token comment&quot;&gt;# line 2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For each line, the system looked through every word (the “keyword”), generating a KWAC entry. This was done by “circularly shifting” words that came before the keyword, appending them to the end. Then, it would sort the entries alphabetically. For example, given the above file, we’d get:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;Criteria. On The &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# &quot;On The Criteria&quot; is the original, &quot;Criteria&quot; was selected as the keyword here&lt;/span&gt;
Development. Program &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
On The Criteria &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
Program Development &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
The Criteria. On &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The next sections explain Parnas’ modularizations. This &lt;a href=&quot;https://youtu.be/R7X4B3-k7g4?t=1036&quot;&gt;video explanation&lt;/a&gt; (starting from 17:16) helped me visualise and understand them, and I’m borrowing liberally from it 🙇‍♂️.&lt;/p&gt;
&lt;h2 id=&quot;modularization-1-flowchart&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#modularization-1-flowchart&quot; aria-label=&quot;modularization 1 flowchart permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Modularization 1: “Flowchart”&lt;/h2&gt;
&lt;p&gt;In the first modularization, Parnas modelled the problem as a flowchart, using the individual steps to break apart the modules (an approach which I’m guilty of applying at face value):&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1afa3be2a535182b9280816c458ea9a8/dd507/Criteria_for_Decomposition_of_Modules_1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.37837837837838%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACLUlEQVQoz32S3UtTYRzHz58Q3UQ36k1BQrWKjIiohPRiES0wgiChoJuCBhatFwQTsUuzIoKom0J2oRKCvWCLrVKb21SUZb6Qgc5tbvMldS/nOecTz3nRZdADh9/znOf7+5zvl99RhC4QmsCsKkJX0ax9ai1NfCVBYi1JIpckKetKgoXsgqFXpd7oEaiyR1dRVPugyaoaInSIZ1Mc6KtmW6Cc0g8OSrx7KfXtZ4t/BzWRWkNT0AqGXvZJmGQoJlkYl3K9mung0vBVlnKLvEv48MY6aY914Z3qpD3eRVusg8D8V9B1E6aZIFUrcihf5LW8AWwYa6H8/SHmVmM0TT7k+tBtboTqcYfucPNbPdcGb/F0+rnlULUc2ilVFGFZzgsT+CD6hINvjjOzOsvlaB3OvvO4ei5y+uMFzvpqOdlfw93xRtA2gMKKLF0qYpPD5rHH7Ht7lMVchlB6mE/Jz/jne/HNBQikeo3zSGYU3YosLIf2UNcjFyyHzdFHVHSe4OfvaSqCTrZ376LstYOStj2UeR1s7dlJzWCt6VAvWA7/iSyHYgKbxlrZ3X2YpXyG78sTDKQiRNLDBFNhwukhBtJhJpYnNw1lw6liT8eO3Dr9gqqQi9mVWc4NXuFI0Eml/wzHuk5R+cVFRX8V7lHPX7+NKI5sb+yLnMiSFWssFZZpmmqhbrwez8h9PMEGPNFG3D/u8ezXSwO44U6sQxVR9AUp0HQN3Xr+t1S9CGRVGfkPs68HBSSIJdkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Criteria for Decomposition of Modules 1.png&quot;
        title=&quot;Criteria for Decomposition of Modules 1.png&quot;
        src=&quot;/static/1afa3be2a535182b9280816c458ea9a8/fcda8/Criteria_for_Decomposition_of_Modules_1.png&quot;
        srcset=&quot;/static/1afa3be2a535182b9280816c458ea9a8/12f09/Criteria_for_Decomposition_of_Modules_1.png 148w,
/static/1afa3be2a535182b9280816c458ea9a8/e4a3f/Criteria_for_Decomposition_of_Modules_1.png 295w,
/static/1afa3be2a535182b9280816c458ea9a8/fcda8/Criteria_for_Decomposition_of_Modules_1.png 590w,
/static/1afa3be2a535182b9280816c458ea9a8/efc66/Criteria_for_Decomposition_of_Modules_1.png 885w,
/static/1afa3be2a535182b9280816c458ea9a8/c83ae/Criteria_for_Decomposition_of_Modules_1.png 1180w,
/static/1afa3be2a535182b9280816c458ea9a8/dd507/Criteria_for_Decomposition_of_Modules_1.png 1528w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;There’s a design flaw that isn’t obvious in the diagram - some common data that all modules need is stored in memory (a modern parallel might be a datastore), each module needing to know the low-level layout of the data. We’ll contrast this with Modularization 2 later.&lt;/p&gt;
&lt;p&gt;Here’s what each module did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Parsed the input text file and stores in memory. We’ll call this “Characters”.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://youtu.be/R7X4B3-k7g4?t=1415&quot;&gt;video (23:36)&lt;/a&gt; that explains this as &lt;strong&gt;(1.)&lt;/strong&gt; memory at the time was accessed in groups of 4 characters (bytes), &lt;strong&gt;(2.)&lt;/strong&gt; the input was split across these groups, with spaces in between words and lines.&lt;/li&gt;
&lt;li&gt;The “Characters” could be visualised as follows:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d8bd433f4cb51feb4e5a1fab55bbb828/fa60d/Criteria_for_Modularization_-_Approach_1_Input_Storage.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 12.837837837837837%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAkUlEQVQI1z3PWQ4CQQgE0Ln/PXWm9+mV3rQMaPwieRQQDuoNpWa83hsx3Vh7ovWGVNLf5h5oVJF/drOtgUoVqURsthgw98TBA84ZrDVwqRPUSZraKIzZxfr4mrFaDj7PB/ok+NvDOCM5NhqEw3krC/m6tRoxR7D54BBigDIKuSZZ6IJFbQWXvqS6X4ZntNXy1Qe+8OYMejNJWwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Criteria for Modularization - Approach 1 Input Storage.png&quot;
        title=&quot;Criteria for Modularization - Approach 1 Input Storage.png&quot;
        src=&quot;/static/d8bd433f4cb51feb4e5a1fab55bbb828/fcda8/Criteria_for_Modularization_-_Approach_1_Input_Storage.png&quot;
        srcset=&quot;/static/d8bd433f4cb51feb4e5a1fab55bbb828/12f09/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 148w,
/static/d8bd433f4cb51feb4e5a1fab55bbb828/e4a3f/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 295w,
/static/d8bd433f4cb51feb4e5a1fab55bbb828/fcda8/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 590w,
/static/d8bd433f4cb51feb4e5a1fab55bbb828/efc66/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 885w,
/static/d8bd433f4cb51feb4e5a1fab55bbb828/c83ae/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 1180w,
/static/d8bd433f4cb51feb4e5a1fab55bbb828/fa60d/Criteria_for_Modularization_-_Approach_1_Input_Storage.png 1792w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Note that in the above, line breaks are not distinguished from spaces. The module outputs a separate “Lines” structure for this, which is an array of “starting positions” for each line (e.g. above, Character 1 is the start of line 1, Character 21 is the start of line 2)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circular Shifter&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Accessing “Characters” in memory and “Lines”, it outputs an array of “Shifts”.&lt;/li&gt;
&lt;li&gt;Each “Shift” stored the line number, as well as the starting index of each word.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alphabetizer&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Accessing “Characters” and “Shifts”, it sorts the “Shifts” alphabetically and outputs them as “Alphabetized”.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Accessing “Characters” and “Alphabetized” shifts, outputs the KWAC index (see “The System: KWAC Index”).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A helpful visual summary of all the outputs and stored data from the video explanation:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a0b3077f3d649c4e682b3f66972f78c4/97755/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.37837837837838%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACX0lEQVQoz3WRS0hUYRiGT16yoMCRpKh1bmrRMhJvac6UiJGZumkXuLSxYmYcRwjbtqhNkJMRVBYUhLQr2xQEugiipdnojDk3z5z7zJzLE+efICH84eHje7/zv3zvf6Smrknq2idp7AoTuBjj6FCC1sEZjgzEaQnFaA5GaO6P1GowSnMwtosoLSGfCIH+CIcv3EFq6Aizr32SQCjKj/UM2eIO6WxBkMkVyMoKuZJKTv6fbKk225YVdMsi+f4LUmNnGOncJC3BKKmtbdIb6/zezpBOb5DeTOG6VWrH2xMPV3zxankVqbFnirruKQIDMTIFGdsuoxkahqmj6SqmaVBSZFRNQdc1FLUk8HtFVVB1lZKqADYvPqwg1ffdor5visDlGFsFmUrFpFDMo6qKMPSNa5dLyKUdofkmuqGhaCXyxTz5YgFwaxse7A3T0HmTY4MRMvkdESGXz2KWTRHF8RxcXGG0sZnCMA2h+0H9JGs/1/i1kRKRFz+uIjX132Z/X4TjQ3PIhgU4qFptA89zcFxb4PemZYiNfd31HKyKhW7q7CjyP8PA6XEOtV3iwMkeFt8uiYG/Tbli4bq2wDe0yqbANxW651CuljEsA0VTxb2X/hu2tpwh2Huens4Ork+ExUC3DCrVCrZjU3WqouriJ2mohiZ6H7NioRk6il4zfP1pBantxBjLX7/z8NkS04k59jxeBdcp49jWLtHF86rYf7V3n78h9Z+a4MGjN8Qj9+gOjTB69znX4vOMxJOC8dkFxmefMpZYYGQ6yXAsydXpJKOJBVGHY/NciT5mbOYJZ2/c5w81UcsnQ6/GvwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Data Structures - On The Criteria for Decomposing Systems - Diego Ongaro.png&quot;
        title=&quot;Data Structures - On The Criteria for Decomposing Systems - Diego Ongaro.png&quot;
        src=&quot;/static/a0b3077f3d649c4e682b3f66972f78c4/fcda8/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png&quot;
        srcset=&quot;/static/a0b3077f3d649c4e682b3f66972f78c4/12f09/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 148w,
/static/a0b3077f3d649c4e682b3f66972f78c4/e4a3f/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 295w,
/static/a0b3077f3d649c4e682b3f66972f78c4/fcda8/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 590w,
/static/a0b3077f3d649c4e682b3f66972f78c4/efc66/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 885w,
/static/a0b3077f3d649c4e682b3f66972f78c4/c83ae/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 1180w,
/static/a0b3077f3d649c4e682b3f66972f78c4/97755/Data_Structures_-_On_The_Criteria_for_Decomposing_Systems_-_Diego_Ongaro.png 2536w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;modularization-2-information-hiding&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#modularization-2-information-hiding&quot; aria-label=&quot;modularization 2 information hiding permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Modularization 2: “Information Hiding”&lt;/h2&gt;
&lt;p&gt;In this modularization, Parnas uses the criteria of “Information Hiding”. The definition is worth mentioning for it’s relevance to the SRP:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every module in the second decomposition is characterized by its knowledge of a design decision which it hides from all others. Its interface or definition was chosen to reveal as little as possible about its inner workings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ec2630668bb8897c5864246f64495fe/85e74/Criteria_for_Decomposing_Modules_2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.4054054054054%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRklEQVQoz42S30uTYRTHdyvrogi6ylp132UIkndKNDfZ1HSsNYV5UyuI/HHhRRTRTX9E+B8E/bBInTq36abzZ+m2CEVBk9z25vR99/54PvG+mxZZ0AOHc57zcD7P4XuOTTc0DKEDBoomk5W+kJWyZH/kyO7nLL9WzLCxv4EQOprQ0IyTpgrV8jYTJquHJo+lYoaajw5qxmqxv7qAfaiWU8MObCPnqIs1osglEFTsz2OAEAKb+di79pTez4OoukosnyRWTBIrpIjtpYhLKaKFaRalZTRFJbwySGOiFfe4n+aIj+YRH86YD1faT6a4hs2EO2MB3BO3+K7kubsywL25PsKJfsLJfu7HB+iZe8izzAvKssyjzBNuznTQGgniGQvgfR/AOxWkfambnJStANsmQ/hGgmzK29QnXVyfcNIw7KT+3Q0a3jZzbbKJ8Gof/3MqwEQPnoivkvlNH6EZlUAT5KSvpAuLzBYWmM3PW5bKp1nILyFrh6hGdSgWcCKEZ7jTqi3rCoqhUNbLlJQSQhisS1tcidZhf32esy8vc3rIwZmhS9jfXMQxfpVd+VvlX0OtAuMhPKOdJ/vXBJgbpcO8tMzo7iSR7ShjO1EiO1HrPrWXQNFlDGGgH3XoTYfoSN9h52AX71w33nSQtngX7vc+Wqe7rAk+Xn3+T93M1TNhutDNPTRYP9hkR95mvbSFK30b14If95Sflg9+WmYCNKXbefCpn3JVK1MO1fhlRzDNBJpCHg9BGH9f3GrOKqoW60I7Blmx5XV+AorZ6g87A8PsAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Criteria for Decomposing Modules 2.png&quot;
        title=&quot;Criteria for Decomposing Modules 2.png&quot;
        src=&quot;/static/5ec2630668bb8897c5864246f64495fe/fcda8/Criteria_for_Decomposing_Modules_2.png&quot;
        srcset=&quot;/static/5ec2630668bb8897c5864246f64495fe/12f09/Criteria_for_Decomposing_Modules_2.png 148w,
/static/5ec2630668bb8897c5864246f64495fe/e4a3f/Criteria_for_Decomposing_Modules_2.png 295w,
/static/5ec2630668bb8897c5864246f64495fe/fcda8/Criteria_for_Decomposing_Modules_2.png 590w,
/static/5ec2630668bb8897c5864246f64495fe/efc66/Criteria_for_Decomposing_Modules_2.png 885w,
/static/5ec2630668bb8897c5864246f64495fe/c83ae/Criteria_for_Decomposing_Modules_2.png 1180w,
/static/5ec2630668bb8897c5864246f64495fe/85e74/Criteria_for_Decomposing_Modules_2.png 1436w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;
Interestingly, what one might typically think as the general “responsibility” &lt;em&gt;remains the same&lt;/em&gt; - modules are still mostly split along the same functionality lines. Instead, the major differences that I noticed in this modularization were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The details of how the characters were stored were encapsulated (hidden) via a new module, “Line Storage”. This meant that other modules did not have to know the low-level layout of the data. Instead, they would have a simpler interface to store and retrieve characters.&lt;/li&gt;
&lt;li&gt;The dependencies of modules were also tweaked, such that only two modules interact directly with “Line Storage”, as opposed to every module having to access “Characters” in Modularization 1.&lt;/li&gt;
&lt;li&gt;Modules in Modularization 1 depended on outputs from previous modules or common in-memory data. In contrast, modules in Modularization 2 depended directly on the interface of other modules (similar to calling methods on the module, a more Object-Oriented style).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each module in greater detail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Like Modularization 1, also parses the input file, but this time initializes a “Line Storage” and inserts the data.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Line Storage&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Think of it like an Object that has methods for inserting and retrieving characters by line (e.g. get Character 5 of Line 1, Word 2).&lt;/li&gt;
&lt;li&gt;Note: Unlike Modularization 1, this means callers do not need to know the nitty-gritty details of how data is stored.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circular Shifter&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Generates the circular shifts from Line Storage in an initialization step.&lt;/li&gt;
&lt;li&gt;Instead of outputting “Shifts”, the module itself provides an interface (like an Object) similar to Line Storage, but allows retrieval of characters by their &lt;em&gt;shifts&lt;/em&gt; instead of lines (e.g. get Character 5 of &lt;em&gt;Shift&lt;/em&gt; 1, Word 2).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alphabetizer&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Retrieving the characters from Circular Shifter, this modules sorts by alphabet and remembers the shifts in an initialization step.&lt;/li&gt;
&lt;li&gt;Instead of outputting the “Alphabetized” shifts, the module also provides an interface for getting ordered Circular Shifts indexes (e.g. tell me the shift number that’s in the 2nd sorted position).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Uses Alphabetizer and Circular Shifter to generate the KWAC Index.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;comparison-changeability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#comparison-changeability&quot; aria-label=&quot;comparison changeability permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Comparison: Changeability&lt;/h2&gt;
&lt;p&gt;While other comparisons were made, the analysis of changeability speaks most to the SRP. Parnas begins by suggesting some “likely” change scenarios. Most require many modules to be updated in Modularization 1, but have a much smaller blast radius in Modularization 2.&lt;/p&gt;
&lt;p&gt;Let’s take Scenario B below as an example - since all the modules in Modularization 1 depended upon a specific layout of “Characters” in memory, changing that layout would require changing every module. However, in Modularization 2, this would only require a change in the implementation of “Line Storage” - the interface could stay the same, meaning other modules were shielded from this change!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Modularization 1&lt;/th&gt;
&lt;th&gt;Modularization 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A. Deciding to store “Characters” in a different medium (e.g. filesystem) instead of memory&lt;/td&gt;
&lt;td&gt;All modules&lt;/td&gt;
&lt;td&gt;Line Storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. Deciding on a different way to store the “Characters” (e.g. group by words instead of every 4 characters)&lt;/td&gt;
&lt;td&gt;All Modules&lt;/td&gt;
&lt;td&gt;Line Storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. Deciding to store the full sentences of each circular shift instead of their indexes&lt;/td&gt;
&lt;td&gt;Circular Shifter, Alphabetizer, Output&lt;/td&gt;
&lt;td&gt;Circular Shifter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D. Deciding to change “Alphabetized” generation to be lazy or distributed (possibly due to a large dataset?)&lt;/td&gt;
&lt;td&gt;Difficult to achieve as computation must be completed before output&lt;/td&gt;
&lt;td&gt;Achievable as Output doesn’t need all the shifts to be “alphabetized”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So the takeaway here is that good “information hiding” (a.k.a the SRP) results in a system that adapts more easily to changes.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So where does the paper leave us in relation to the SRP? Personally, I left with a better understanding of &lt;em&gt;why&lt;/em&gt; it was important - it should result in a system that is easier to evolve with changes in requirements.&lt;/p&gt;
&lt;p&gt;However, even with the (helpful) example, we are left with an abstract criterion of “information-hiding” and starting with a “list of difficult design decisions” or “design decisions which are likely to change”. The SRP, it seems, remains more art than science.&lt;/p&gt;
&lt;p&gt;Discuss on &lt;a href=&quot;https://news.ycombinator.com/item?id=30529854&quot;&gt;Hackernews&lt;/a&gt;, or continue the conversation on &lt;a href=&quot;https://twitter.com/wasabigeek/status/1500047320443850753?ref_src=twsrc%5Etfw&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(I apologise for the mix of American and British english 😆.)&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#further-reading&quot; aria-label=&quot;further reading permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Further Reading:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Diego Ontaro’s &lt;a href=&quot;https://youtu.be/R7X4B3-k7g4?t=1036&quot;&gt;talk&lt;/a&gt; and his example &lt;a href=&quot;https://github.com/ongardie/kwic&quot;&gt;code&lt;/a&gt; in Go.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Key_Word_in_Context&quot;&gt;KWIC Indexes in Wikipedia&lt;/a&gt; - note that the original paper calls it a KWIC index, but as Diego Ontaro explains, it’s actually a KWAC index.&lt;/li&gt;
&lt;li&gt;Parnas’ Original Papers: &lt;a href=&quot;https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf&quot;&gt;1972&lt;/a&gt; (which references an earlier paper written in &lt;a href=&quot;https://prl.ccs.neu.edu/img/p-tr-1971.pdf&quot;&gt;1971&lt;/a&gt; with more implementation details).&lt;/li&gt;
&lt;li&gt;Adrian Colyer’s elegant and higher-level &lt;a href=&quot;https://blog.acolyer.org/2016/09/05/on-the-criteria-to-be-used-in-decomposing-systems-into-modules/&quot;&gt;summary&lt;/a&gt; of the same paper.&lt;/li&gt;
&lt;li&gt;The paper does make a few other comparisons and points, I’ve left them out to keep this more focused to the SRP. A summary of what’s missing can be found in this &lt;a href=&quot;https://gist.github.com/wasabigeek/08a09190a90ac4463660debce778ef31&quot;&gt;gist&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Reading a Ruby gem with VSCode]]></title><description><![CDATA[Whether out of curiosity or trying to understand a method for debugging, it’s helpful to know how to dig into a Ruby gem. Recently I was…]]></description><link>https://www.wasabigeek.com/blog/reading-a-ruby-gem-with-vscode/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/reading-a-ruby-gem-with-vscode/</guid><pubDate>Tue, 15 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Whether out of curiosity or trying to understand a method for debugging, it’s helpful to know how to dig into a Ruby gem. Recently I was curious over how &lt;a href=&quot;https://github.com/freerange/mocha/&quot;&gt;mocha&lt;/a&gt;, a mocking library for minitest, allowed stubbing on &lt;code class=&quot;language-text&quot;&gt;any_instance&lt;/code&gt; of a Class (as opposed to injecting a stubbed object), for example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_stubbing_an_instance_method_on_all_instances_of_a_class&lt;/span&gt;&lt;/span&gt;
  Product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;any_instance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stubs&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;returns&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;stubbed_name&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  product &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
  assert_equal &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;stubbed_name&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Following this are steps you can try when investigating a gem. I’d love to say these were the exact steps I took while investigating &lt;code class=&quot;language-text&quot;&gt;any_instance&lt;/code&gt;, but truth is some steps I discovered only while writing this post 😅. The steps are specific to using VSCode.&lt;/p&gt;
&lt;h2 id=&quot;open-the-source-code&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#open-the-source-code&quot; aria-label=&quot;open the source code permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Open the Source Code&lt;/h2&gt;
&lt;p&gt;Assuming the gem is installed, &lt;code class=&quot;language-text&quot;&gt;gem open &amp;lt;gemname&gt;&lt;/code&gt; (or &lt;code class=&quot;language-text&quot;&gt;bundle open &amp;lt;gemname&gt;&lt;/code&gt; if installed in a project via Bundler) will open it’s source code with the command set in the &lt;code class=&quot;language-text&quot;&gt;$EDITOR&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;p&gt;For example &lt;code class=&quot;language-text&quot;&gt;EDITOR=code bundle open mocha&lt;/code&gt; from my project opens the gem in VSCode:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c55ef4a8be41a2d8f8603a57ddde1b4d/69d6b/Mocha_opened_in_VSCode.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.7027027027027%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABO0lEQVQY01XQyY6bQABFUf6jxWCDoZgxRc2Am8StTOpONvn/T7lRnEidLI7eXb9IrgolNbOUzPMfclmw1j5s+45znrCuaG24Xmeu8/y+f1vKBaU0kRwn9CIRl4JWVA/FpWSWC10/UJUlbS1ohKAW4tFD29D/o20apnFk6HuiIdxZjm8krSLrDdlgSU4Flagx4WB1jmdnuDnLai1H8NxC4L5vfNw2XoLldbe83Sxq6IjK/Y32/pNzeKXYvpNvP3g6V9T5iS+r4VMwfA6GF2/4ujkO7/FuRdkd4zacWzFK80FfEUVOdO07ji2g54lOVNSXnDSJKcsK7zeM8Xi/Y7RFLQr9+1+laWdHIwOd3qll4FyUJHFMNE4z++2ZrhvIi/Ihy1JOF0E+BfLRkpYdT0n2nzjNiJP0XZyQJAm/AGzdu+jH1p5NAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Mocha opened in VSCode.png&quot;
        title=&quot;Mocha opened in VSCode.png&quot;
        src=&quot;/static/c55ef4a8be41a2d8f8603a57ddde1b4d/fcda8/Mocha_opened_in_VSCode.png&quot;
        srcset=&quot;/static/c55ef4a8be41a2d8f8603a57ddde1b4d/12f09/Mocha_opened_in_VSCode.png 148w,
/static/c55ef4a8be41a2d8f8603a57ddde1b4d/e4a3f/Mocha_opened_in_VSCode.png 295w,
/static/c55ef4a8be41a2d8f8603a57ddde1b4d/fcda8/Mocha_opened_in_VSCode.png 590w,
/static/c55ef4a8be41a2d8f8603a57ddde1b4d/efc66/Mocha_opened_in_VSCode.png 885w,
/static/c55ef4a8be41a2d8f8603a57ddde1b4d/c83ae/Mocha_opened_in_VSCode.png 1180w,
/static/c55ef4a8be41a2d8f8603a57ddde1b4d/69d6b/Mocha_opened_in_VSCode.png 1912w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If the gem isn’t installed, you could also look up the source code online (&lt;a href=&quot;https://rubygems.org/&quot;&gt;rubygems.org&lt;/a&gt;) and if the code is on GitHub, there’s a handy &lt;a href=&quot;https://twitter.com/github/status/1425505817827151872?s=21&quot;&gt;shortcut&lt;/a&gt; to open it in a browser version of VSCode.&lt;/p&gt;
&lt;h2 id=&quot;find-your-target&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#find-your-target&quot; aria-label=&quot;find your target permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Find your Target&lt;/h2&gt;
&lt;p&gt;One of the easiest ways to find the method definition is to use &lt;code class=&quot;language-text&quot;&gt;#source_location&lt;/code&gt;. Fire up an irb console:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;minitest&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;mocha/minitest&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:any_instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source_location
&lt;span class=&quot;token comment&quot;&gt;# =&gt; [&quot;/.../ruby/3.0.3/gems/mocha-1.13.0/lib/mocha/class_methods.rb&quot;, 45]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In case that doesn’t work, since we have the source code open, we can make use of VSCode’s search:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/486ed4d7639cc618d193928c541391cc/dcb79/Searching_Mocha_in_VSCode.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.97297297297297%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADE0lEQVQ4y12TSW/kVBRG/ReQaHWnBttvsv3sqvJcLtcYOiGJkjSNUFogYBEl6gVEDK2OhNJCwBrW/N6D7EoEYvHp3rt4d3zHmc/nzGYpaZoRhiHWxmRpRlXVzLKCej5n2bZMk4RZEJDnOVVVEUURWhuMCQiCsFfnO9vtlrquOT05oVmumOQ5kbXYsuGFDHg+OODZwQFi5JIWKUWRobVGStk3oJTqbV9AGZymaairmvXukMt6zuui5KfbW/66v+f3uzse3r7ll5sb/nj3ns3uE6QJGY/HjIYjRqNHDYcMH2OnLEvKsmKz3XG4aFmVNT/e3PLnw298+OEd99fXvL++5tfv7livFmijSNIZSkl830dIgS8kQvh4novT7WiaTKiLkrwoSKsFelLhhRMORmNeDAZ89OxjRp7CpgVxmjFJp8SJxdoAG4cYG2NtiJQezrxpSCLLumk53Sy42NSU6ZQknqC0Yey6KNntKWEsNK6v8FSEKzTD4XA/+pMdjXG6g8xsxK5t2Ww2nO9aXq5LNk3J8bLh4uiIMAiQ2kcagVDdmF4f730foffWlz5O0y4x+Yrlp684Prvgy4szfj4957OXF3z/9bf8/fCBIs/QiSCcGIxVqC6x9lGh7ItILVCB6H1nsdrgLy85+eIb3ry54vj8iqujz9ldfkVYrng+GPSVo9T0j1zpMxY+vvD+7fix+y6x07YtaRTyettwuWtYtgvKuuJkWVBPI0R3QSkIEk2cBbSZZlVogkgiH7vqCnXyfA9n0VEQJxyudyw3O7brNet5yemyZJ4mCCH6LyEDHx35xIkkKwKCWPV79KWH67q9uv/p7DGyPW55lnNYzdhVU7K8IAgtxpiehrzKWB1tyOc5KpIoq5GBQiiB63p43l5Ox6YJOoR0z6Z6lNGmx0sbgxSCNJ+zOntFtl5jE0sxtcysYRqHJKEhMpo4NF3Cogf7CfT/qkvYjdxVTiczsiwlnqXUacxRZSmSkDiJUGI/tt+R0nX4/4RPvpSqH7k7zCIvWOUxMxswVoqh0Xja4CvN2B33++t2+A+czczXgABk5AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Searching Mocha in VSCode.png&quot;
        title=&quot;Searching Mocha in VSCode.png&quot;
        src=&quot;/static/486ed4d7639cc618d193928c541391cc/fcda8/Searching_Mocha_in_VSCode.png&quot;
        srcset=&quot;/static/486ed4d7639cc618d193928c541391cc/12f09/Searching_Mocha_in_VSCode.png 148w,
/static/486ed4d7639cc618d193928c541391cc/e4a3f/Searching_Mocha_in_VSCode.png 295w,
/static/486ed4d7639cc618d193928c541391cc/fcda8/Searching_Mocha_in_VSCode.png 590w,
/static/486ed4d7639cc618d193928c541391cc/efc66/Searching_Mocha_in_VSCode.png 885w,
/static/486ed4d7639cc618d193928c541391cc/c83ae/Searching_Mocha_in_VSCode.png 1180w,
/static/486ed4d7639cc618d193928c541391cc/dcb79/Searching_Mocha_in_VSCode.png 1700w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Some ideas to try if the results list is really long:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prefixing the search term with the declaration syntax (e.g. &lt;code class=&quot;language-text&quot;&gt;class &amp;lt;ClassName&gt;&lt;/code&gt; for a class, &lt;code class=&quot;language-text&quot;&gt;def &amp;lt;method_name&gt;&lt;/code&gt; for an instance method) generally works well, but not always, given the many ways to metaprogram in Ruby. For example, it’s not obvious that &lt;code class=&quot;language-text&quot;&gt;any_instance&lt;/code&gt; would have been defined as an instance method.&lt;/li&gt;
&lt;li&gt;”&lt;strong&gt;Match Whole Word&lt;/strong&gt;” (ab) helps exclude classes / methods with the search term as a substring (e.g. it would have excluded &lt;code class=&quot;language-text&quot;&gt;any_instance_method&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;mock_impersonating_any_instance_of&lt;/code&gt; in the results above)&lt;/li&gt;
&lt;li&gt;”&lt;strong&gt;files to include&lt;/strong&gt;” knowing the structure of most gems (see later section), we can usually restrict to the &lt;code class=&quot;language-text&quot;&gt;lib/&lt;/code&gt; directory, which will exclude test files, bin scripts etc&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;trace-the-callers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trace-the-callers&quot; aria-label=&quot;trace the callers permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trace the Callers&lt;/h2&gt;
&lt;p&gt;While we found the method, it didn’t answer our initial question, which was how &lt;code class=&quot;language-text&quot;&gt;any_instance&lt;/code&gt; becomes callable on any class. We could probably try a “top-down” or “bottom-up” search here.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;“bottom-up” search&lt;/strong&gt;, since this is plain Ruby as opposed to Rails, we could try to follow the &lt;code class=&quot;language-text&quot;&gt;require&lt;/code&gt; trail. But if we have an extension like &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=castwide.solargraph&quot;&gt;solargraph&lt;/a&gt;, a simpler way is to make use of VSCode’s &lt;strong&gt;Go to References&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/034c60797b097c8776c230f2742c0a7b/bb543/VSCode_Go_To_References_in_Mocha.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.72972972972974%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJElEQVQoz5WRS2/TQBRG3TQJr5bSAkWCxDPjebg4TuLYsZM0jpO2VG3oA9QdLPj//+Igu9AFOxZH82mu7tG9M54vBKF1JMkYl8UETiFlDxmIJistG+qsXUBgFDoMUEahjHyq19mFDk/4PkL4DKKQwThmMJsSTVOkkgjRR0ofUSP6+H6PXu8jvughZb+5+0tds9bgneTnJOWWvLpnWN4xqm4Yrb7isjN0UhEkK1y6xk0vGJW3pJtvxMtbRHKGP1qjkg1yvEGMNwxmF3hdM2d/eM7r/J73cUUwqRDJmuN4xdtBycHnJa2g4MPkgtXtDy4efnHz8JOz6++MV1vc/Ipwcc3J4orpZovniZRnesZ+uOL5YEnX5uzKlF2Z0Qky2jLD66ccqph8mrGuKu7Ol9yfLyjnOS48wTmHC0PSLKuFdWPO69GcvfSU7nBBJ5rzMp7xajjjRVTQ0jlvVMwojpkVBV8WGZeLlGwUo43FGovRljge4u3IKV2dcxiVRGXB9X3KZpux7ya0REpHpbRkxrsgxmqNCjRCPRJog7WuQWtDFA3wdlX+KPxc4ucVqir5VJzyzBZ4/oQdkVJvcRTEhNY2ze4PdTbGPp316l5HF3RNQTcoeD/Zcrz8xvHihrfZJQfxqnk/z59yqIaN0Bj3NJX9R1pP6XXNjK5+FO6FJW1TsBPMaOs5HTOnpfJmwv8TmqL5mKNojVpeIco7DpJL2mZBx87/S/gbAipCPNfy7t8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;VSCode Go To References in Mocha.png&quot;
        title=&quot;VSCode Go To References in Mocha.png&quot;
        src=&quot;/static/034c60797b097c8776c230f2742c0a7b/fcda8/VSCode_Go_To_References_in_Mocha.png&quot;
        srcset=&quot;/static/034c60797b097c8776c230f2742c0a7b/12f09/VSCode_Go_To_References_in_Mocha.png 148w,
/static/034c60797b097c8776c230f2742c0a7b/e4a3f/VSCode_Go_To_References_in_Mocha.png 295w,
/static/034c60797b097c8776c230f2742c0a7b/fcda8/VSCode_Go_To_References_in_Mocha.png 590w,
/static/034c60797b097c8776c230f2742c0a7b/efc66/VSCode_Go_To_References_in_Mocha.png 885w,
/static/034c60797b097c8776c230f2742c0a7b/c83ae/VSCode_Go_To_References_in_Mocha.png 1180w,
/static/034c60797b097c8776c230f2742c0a7b/bb543/VSCode_Go_To_References_in_Mocha.png 1418w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Above, we find that &lt;code class=&quot;language-text&quot;&gt;Mocha::ClassMethods&lt;/code&gt; is included into Class in &lt;code class=&quot;language-text&quot;&gt;Mocha::API&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:include&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ClassMethods&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can continue doing the same approach, but at some point the “search tree” got quite unwieldy so I switched to a more &lt;strong&gt;“top-down” approach&lt;/strong&gt;, checking the README to see how the gem was &lt;code class=&quot;language-text&quot;&gt;require&lt;/code&gt;-d.&lt;/p&gt;
&lt;h2 id=&quot;know-the-structure-of-a-gem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#know-the-structure-of-a-gem&quot; aria-label=&quot;know the structure of a gem permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Know the Structure of a Gem&lt;/h2&gt;
&lt;p&gt;A quick detour on how a gem is structured - &lt;code class=&quot;language-text&quot;&gt;lib/&lt;/code&gt; generally contains the gem’s core logic. There you’ll find a ruby file and a folder corresponding to the name of the gem. For example, in &lt;code class=&quot;language-text&quot;&gt;mocha&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;lib/
└── mocha.rb
└── mocha/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The ruby file (e.g. &lt;code class=&quot;language-text&quot;&gt;lib/mocha.rb&lt;/code&gt;) generally contains most of the gem’s bootstrapping logic, as this is the file loaded when requiring the gem (i.e. &lt;code class=&quot;language-text&quot;&gt;require &apos;mocha&apos;&lt;/code&gt; would load &lt;code class=&quot;language-text&quot;&gt;lib/mocha.rb&lt;/code&gt;). So typically we’d start our “top-down” search here.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;mocha&lt;/code&gt; is slightly different - it supports multiple test libraries, and in the &lt;a href=&quot;https://github.com/freerange/mocha/#minitest-1&quot;&gt;README&lt;/a&gt; we are instructed to require a different file depending on our test library. For example, for &lt;code class=&quot;language-text&quot;&gt;minitest&lt;/code&gt; we’d do:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;minitest/unit&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;mocha/minitest&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So in this case we’d start our search from &lt;code class=&quot;language-text&quot;&gt;lib/mocha/minitest.rb&lt;/code&gt;. There on it’s pretty straightforward Ruby, leading us back to &lt;code class=&quot;language-text&quot;&gt;Mocha::API&lt;/code&gt; (albeit with knowledge of how it gets required!).&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#footnotes&quot; aria-label=&quot;footnotes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Footnotes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.justinweiss.com/articles/how-to-dispel-ruby-magic-and-understand-your-gems&quot;&gt;How to Dispel Ruby Magic and Understand Your Gems (Justin Weiss)&lt;/a&gt; - an inspiration for much of this&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://guides.rubygems.org/what-is-a-gem/&quot;&gt;Structure of a Gem (RubyGems)&lt;/a&gt; - more details on how a gem is structured, along with a quick start so you can build your own gem!&lt;/li&gt;
&lt;li&gt;VSCode docs on &lt;a href=&quot;https://code.visualstudio.com/docs/editor/codebasics#_search-across-files&quot;&gt;searching across files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Practical Metaprogramming in Ruby: minitest/mock]]></title><description><![CDATA[(repo) is a tiny mocking library (< 250 lines of code, including comments) that’s also a great showcase for how metaprogramming can be used…]]></description><link>https://www.wasabigeek.com/blog/practical-metaprogramming-in-ruby-minitest-mock/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/practical-metaprogramming-in-ruby-minitest-mock/</guid><pubDate>Sat, 22 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;minitest/mock&lt;/code&gt; (&lt;a href=&quot;https://github.com/seattlerb/minitest/blob/v5.15.0/lib/minitest/mock.rb&quot;&gt;repo&lt;/a&gt;) is a tiny mocking library (&amp;#x3C; 250 lines of code, &lt;em&gt;including&lt;/em&gt; comments) that’s also a great showcase for how metaprogramming can be used in Ruby - let’s take a look! If you’re new-ish to Ruby, I hope this gives you concrete examples of what metaprogramming is possible, and how to do it.&lt;/p&gt;
&lt;h2 id=&quot;what-is-metaprogramming&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-metaprogramming&quot; aria-label=&quot;what is metaprogramming permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is metaprogramming?&lt;/h2&gt;
&lt;p&gt;In the context of this article, I’m referring to programs and code that are able to do &lt;em&gt;introspection&lt;/em&gt; (inspect themselves e.g. type or properties) and &lt;em&gt;reflection&lt;/em&gt; (modify their behaviour).&lt;/p&gt;
&lt;h2 id=&quot;first-example-set-expectations-with-method_missing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#first-example-set-expectations-with-method_missing&quot; aria-label=&quot;first example set expectations with method_missing permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;First example: Set expectations with method_missing&lt;/h2&gt;
&lt;p&gt;The main feature of &lt;code class=&quot;language-text&quot;&gt;minitest/mock&lt;/code&gt; is the &lt;code class=&quot;language-text&quot;&gt;Minitest::Mock&lt;/code&gt; class, which can be used to test that a method is called, for example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;minitest/mock&apos;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClassUnderTest&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;foo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@foo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; foo
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;call&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@foo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bar
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# create an instance of Mock and set an expectation&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# that the method `bar` will be called on it&lt;/span&gt;
mock_foo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Minitest&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Mock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
mock_foo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expect&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:bar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;example_return_value&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# inject the mock into the Object-Under-Test&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# which essentially calls `mock_foo.bar`&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;ClassUnderTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mock_foo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;call &lt;span class=&quot;token comment&quot;&gt;# =&gt; returns &apos;example_return_value&apos;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# check that `bar` was called&lt;/span&gt;
mock_foo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;verify&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that there’s nowhere in the library where the method &lt;code class=&quot;language-text&quot;&gt;bar&lt;/code&gt; is explicitly defined, yet running the above doesn’t raise a &lt;code class=&quot;language-text&quot;&gt;NoMethodError&lt;/code&gt;. We were able “define” (well, not exactly, but we’ll get to that) a new method &lt;em&gt;after&lt;/em&gt; the mock object was already instantiated, via the &lt;code class=&quot;language-text&quot;&gt;expect&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;The truth is &lt;code class=&quot;language-text&quot;&gt;expect&lt;/code&gt; (&lt;a href=&quot;https://github.com/seattlerb/minitest/blob/v5.15.0/lib/minitest/mock.rb#L81-L92&quot;&gt;source code&lt;/a&gt;) is straightforward Ruby; it saves the expected call to an Array instance variable - no new method gets defined. The real magic happens in &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; :&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# lib/minitest/mock.rb#L118&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;method_missing&lt;/span&gt;&lt;/span&gt; sym&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  expected_call &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@expected_calls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sym&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  expected_args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; retval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; val_block &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; expected_call&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;values_at&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:args&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:retval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@actual_calls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;sym&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:retval&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; retval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:args&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; zipped_args&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;mod&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; mod &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; mod &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; a &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If a method is called that doesn’t exist, Ruby will invoke &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; with a few arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the method’s name in symbol form (&lt;code class=&quot;language-text&quot;&gt;sym&lt;/code&gt; in the above example)&lt;/li&gt;
&lt;li&gt;the arguments (&lt;code class=&quot;language-text&quot;&gt;*args&lt;/code&gt; ) and block (&lt;code class=&quot;language-text&quot;&gt;&amp;amp;block&lt;/code&gt;) passed to the non-existent method&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Minitest::Mock&lt;/code&gt; uses this to catch any undefined method that is called, checking it against the saved expectations. This makes it seem like the object has that method defined!&lt;/p&gt;
&lt;h2 id=&quot;remove-inherited-methods-via-undef_method&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#remove-inherited-methods-via-undef_method&quot; aria-label=&quot;remove inherited methods via undef_method permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Remove inherited methods via undef_method&lt;/h2&gt;
&lt;p&gt;While &lt;code class=&quot;language-text&quot;&gt;Minitest::Mock&lt;/code&gt; doesn’t explicitly define many methods, it does inherit methods from Ruby’s &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; class, such as &lt;code class=&quot;language-text&quot;&gt;#nil?&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What if we want to check that a mock receives a method call, like &lt;code class=&quot;language-text&quot;&gt;#nil?&lt;/code&gt;? Without any intervention, &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt; would &lt;em&gt;not&lt;/em&gt; be invoked, since those methods exist! Something like this would happen:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expects&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:nil?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# =&gt; false (this is the default for Object!)&lt;/span&gt;
mock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;verify &lt;span class=&quot;token comment&quot;&gt;# raises MockExpectationError, because the original `#nil?` method is called instead of `method_missing`!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So what the library does here is to undefine them when the class is interpreted. &lt;code class=&quot;language-text&quot;&gt;instance_methods&lt;/code&gt; is used to introspect the class for any instance methods, which are removed via &lt;code class=&quot;language-text&quot;&gt;undef_method&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# lib/minitest/mock.rb#L26&lt;/span&gt;
instance_methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;m&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  undef_method m &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; overridden_methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; m &lt;span class=&quot;token operator&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^__/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Calling &lt;code class=&quot;language-text&quot;&gt;#nil?&lt;/code&gt; now would properly invoke &lt;code class=&quot;language-text&quot;&gt;method_missing&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;redefining-methods-with-define_method&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#redefining-methods-with-define_method&quot; aria-label=&quot;redefining methods with define_method permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redefining Methods with define_method&lt;/h2&gt;
&lt;p&gt;In the code above, we saw that not all &lt;code class=&quot;language-text&quot;&gt;instance_methods&lt;/code&gt; were removed, there are some &lt;code class=&quot;language-text&quot;&gt;overridden_methods&lt;/code&gt; (e.g. &lt;code class=&quot;language-text&quot;&gt;inspect&lt;/code&gt;) where the library opts to keep the default implementation &lt;em&gt;unless&lt;/em&gt; the user has explicitly set an expectation. This is done by re-defining them via &lt;code class=&quot;language-text&quot;&gt;define_method&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# lib/minitest/mock.rb#L30&lt;/span&gt;
overridden_methods&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:to_sym&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;method_id&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;define_method&lt;/span&gt; method_id &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;b&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;	
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@expected_calls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; method_id &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
  	  method_missing&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
  	  &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The goal seems to be so that a &lt;code class=&quot;language-text&quot;&gt;Minitest::Mock&lt;/code&gt; instance still responds like a Ruby object e.g. when printing it in the terminal 😄.&lt;/p&gt;
&lt;h2 id=&quot;stubbing-by-redefining-methods-on-metaclasses&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stubbing-by-redefining-methods-on-metaclasses&quot; aria-label=&quot;stubbing by redefining methods on metaclasses permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stubbing by redefining methods on “Metaclasses”&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;minitest/mock&lt;/code&gt; also has a stubbing feature, which allows you to redefine the return value of an object within a block:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;example&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now &lt;span class=&quot;token comment&quot;&gt;# =&gt; &quot;example&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now &lt;span class=&quot;token comment&quot;&gt;# =&gt; actual time...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s look at the implementation - the library first opens the &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; class, and defines a &lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt; method there:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# lib/minitest/mock.rb#L214&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stub&lt;/span&gt;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; val_or_callable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;block_args
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since &lt;code class=&quot;language-text&quot;&gt;Object&lt;/code&gt; is the ancestor of all almost everything in Ruby (including Classes and Modules), we are able to call &lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt; on basically anything. The actual implementation of &lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt; is the interesting bit, introducing us to Ruby’s “metaclasses” (also known as “singleton classes” or “eigenclasses”):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# lib/minitest/mock.rb#L214&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;stub&lt;/span&gt;&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; val_or_callable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;block_args
  	new_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;__minitest_stub__&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
	
  	metaclass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  	&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send &lt;span class=&quot;token symbol&quot;&gt;:alias_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; new_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name

  	metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send &lt;span class=&quot;token symbol&quot;&gt;:define_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;blk&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
	  &lt;span class=&quot;token comment&quot;&gt;# return or execute val_or_callable&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# cleanup...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There’s some further reading on metaclasses and that mysterious &lt;code class=&quot;language-text&quot;&gt;class &amp;lt;&amp;lt; self&lt;/code&gt; syntax in the footnotes, but one way to understand the above is to replace the following lines:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;metaclass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send &lt;span class=&quot;token symbol&quot;&gt;:define_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;blk&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;…with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;blk&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In other words, &lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt; is defining (or overwriting) the method on the object - the type of method (class/instance) being neatly handled by accessing the metaclass. (Note: &lt;code class=&quot;language-text&quot;&gt;self.define_method&lt;/code&gt; would not work for class methods, since &lt;code class=&quot;language-text&quot;&gt;self&lt;/code&gt; returns the class, it would define instance methods.)&lt;/p&gt;
&lt;p&gt;The original logic is kept safe by renaming it via &lt;code class=&quot;language-text&quot;&gt;alias_method&lt;/code&gt;, and in the &lt;code class=&quot;language-text&quot;&gt;ensure&lt;/code&gt; block, there is code to reverse this change. These ensure stubbing only takes effect in the stub block.&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#footnotes&quot; aria-label=&quot;footnotes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Footnotes&lt;/h2&gt;
&lt;p&gt;Note that metaprogramming, while powerful, comes with a performance and readability cost. Use with care!&lt;/p&gt;
&lt;p&gt;For further reading on various topics touched in this article, check these out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/mocksArentStubs.html&quot;&gt;Mocks aren’t Stubs&lt;/a&gt; by Martin Fowler&lt;/li&gt;
&lt;li&gt;Notes on &lt;a href=&quot;https://cs.lmu.edu/~ray/notes/metaprogramming/&quot;&gt;Metaprogramming&lt;/a&gt; by Ray Toal&lt;/li&gt;
&lt;li&gt;Ruby metaclasses:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.toptal.com/ruby/ruby-metaprogramming-cooler-than-it-sounds&quot;&gt;Ruby Metaprogramming Is Even Cooler Than It Sounds&lt;/a&gt; by Nikola Todorovic&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@leo_hetsch/demystifying-singleton-classes-in-ruby-caf3fa4c9d91&quot;&gt;Diving into Ruby Singleton Classes&lt;/a&gt; by Léonard Hetsch&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/&quot;&gt;Metaprogramming in Ruby: It’s All About the Self&lt;/a&gt; by Yehuda Katz&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Mocking in RSpec VS Minitest: A Cheatsheet]]></title><description><![CDATA[My first exposure to a testing library in Ruby and Rails was RSpec, but I’m now using Minitest. It wasn’t immediately obvious how to…]]></description><link>https://www.wasabigeek.com/blog/mocking-in-rspec-and-minitest-cheatsheat/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/mocking-in-rspec-and-minitest-cheatsheat/</guid><pubDate>Wed, 05 Jan 2022 23:21:03 GMT</pubDate><content:encoded>&lt;p&gt;My first exposure to a testing library in Ruby and Rails was RSpec, but I’m now using Minitest. It wasn’t immediately obvious how to translate rspec-expectations and rspec-mocks to Minitest, so here’s my attempt at a cheatsheet:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RSpec&lt;/th&gt;
&lt;th&gt;minitest with &lt;a href=&quot;https://github.com/freerange/mocha&quot;&gt;mocha&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;minitest with &lt;a href=&quot;https://github.com/seattlerb/minitest#mocks-&quot;&gt;minitest/mocks&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;double&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Minitest::Mock.new&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;instance_double&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;stub&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;mock.responds_like_instance_of&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;expect().to receive().with().and_return()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock.expects().with().returns()&lt;/code&gt;&lt;br/&gt;Note:&lt;ul&gt;&lt;li&gt;like RSpec, can also call &lt;code class=&quot;language-text&quot;&gt;expects&lt;/code&gt; on any object, not just a &lt;code class=&quot;language-text&quot;&gt;Mock&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;Stub&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;with&lt;/code&gt; has &lt;a href=&quot;https://github.com/freerange/mocha/issues/446#issuecomment-1006680273&quot;&gt;imprecise keyword arg matching&lt;/a&gt; in Ruby 3&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock = Minitest::Mock.new&lt;/code&gt;&lt;br/&gt;&lt;code class=&quot;language-text&quot;&gt;mock.expect(:method, args, return)&lt;/code&gt;&lt;br/&gt;&lt;code class=&quot;language-text&quot;&gt;mock.verify&lt;/code&gt;&lt;br/&gt;Note: cannot call &lt;code class=&quot;language-text&quot;&gt;expect&lt;/code&gt; on any Object, but can make a Mock &lt;a href=&quot;https://github.com/seattlerb/minitest/blob/v5.15.0/lib/minitest/mock.rb#L120-L121&quot;&gt;delegate to an underlying object&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;expect().not_to receive()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock.expects().never&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Technically you could use &lt;a href=&quot;https://github.com/seattlerb/minitest/blob/v5.15.0/lib/minitest/mock.rb#L214&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Object#stub&lt;/code&gt;&lt;/a&gt; and raise in the &lt;code class=&quot;language-text&quot;&gt;val_or_callable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;expect()... and yield_control()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock.expects().yields(*parameters)&lt;/code&gt; &lt;br/&gt;See &lt;a href=&quot;https://www.rubydoc.info/github/floehopper/mocha/Mocha%2FExpectation:yields&quot;&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Not supported since return value is not a callable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;expect()... and raise_error()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;mock.expects().raises(error)&lt;/code&gt; &lt;br/&gt;See &lt;a href=&quot;https://www.rubydoc.info/github/floehopper/mocha/Mocha%2FExpectation:raises&quot;&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Can pass in a block (instead of expected args) to &lt;code class=&quot;language-text&quot;&gt;expect&lt;/code&gt; and raise there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;allow()...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Similar as above but substitute &lt;code class=&quot;language-text&quot;&gt;expects&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;stubs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Can use &lt;a href=&quot;https://github.com/seattlerb/minitest/blob/v5.15.0/lib/minitest/mock.rb#L214&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Object#stub&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;.and_call_original&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not supported (see &lt;a href=&quot;https://github.com/freerange/mocha/issues/334&quot;&gt;reasoning&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;allow_any_instance_of.Klass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;Klass.any_instance.stubs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It’s very possible I’ve gotten something wrong - please feel free to correct in the comments or submit a PR!&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#footnotes&quot; aria-label=&quot;footnotes permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Footnotes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For the curious, I wrote about how &lt;code class=&quot;language-text&quot;&gt;minitest/mock&lt;/code&gt; works &lt;a href=&quot;https://wasabigeek.com/blog/practical-metaprogramming-in-ruby-minitest-mock/&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[My Learning Workflow for 2022]]></title><description><![CDATA[I’ve been working through a course on Coursera recently, and it’s been a great opportunity to reflect on where I am in the “meta” of…]]></description><link>https://www.wasabigeek.com/blog/my-learning-workflow-for-2022/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/my-learning-workflow-for-2022/</guid><pubDate>Fri, 31 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been working through a course on Coursera recently, and it’s been a great opportunity to reflect on where I am in the “meta” of learning. This post walk throughs my current workflow before diving into the context behind it.&lt;/p&gt;
&lt;h2 id=&quot;1-the-workflow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-the-workflow&quot; aria-label=&quot;1 the workflow permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. The Workflow&lt;/h2&gt;
&lt;p&gt;The workflow is loosely based on Zettelkasten, with a little thrown in from a “Learning to Learn” course I audited last year. I’m currently using two tools: &lt;strong&gt;Obsidian&lt;/strong&gt; for note-taking, and &lt;strong&gt;Anki&lt;/strong&gt; for testing myself / spaced-repetition. More on these in later sections.&lt;/p&gt;
&lt;p&gt;My Obsidian knowledge base is about 6 months old (I’ve yet to migrate my older knowledge base) and looks like this, with a few top level folders:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/aa9cafa6bf9fc3f058d98289674c84eb/c4842/ObsidianDirectoryStructure2021.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.91891891891892%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACVklEQVQ4y22T2XKbQBBFsfVile2ytoFB7DPsMAKEUOTEWf7/p26qG0lOUnk4dZlp6tIb1tZ5w9tqhcXjIx6vLBYLPDw84OXlBUpppGmGzWaL3XbHbP9htxOwhQ0hbFirtxUcx4EjJdz9nnUnBDabDXw/xOXLB47DGVVpYNoBTd3NNB3apodpe45FoUISZ7CCIGAj6TgIXJfxpYS0bSRJgvP5jMvlgq7vYEwLczBo2wZdd+C7ruvQti3qpub3Lc/zoLRG5EjkUqJyXbR7D5F0odMMp9MJ0zThcDhc6a4cMAwDG95iURTBEkIgTVPsPQ9734cXBFhvtxC2DaUUjDGcQVVVrE3ToK5rhs4UpzuKk49lC4Esy5Aode8j3RH0xbnkd5ymiTOdMz5jHMe73qogtWggWikkvg8tJRJHcnaUeRiGXNY0jSirAmmqkGWaSdNZdaqR5zlDiVm246DQGtp1kUiJWJKhuBuO44Tx+AN9945j/w3T+BPT+At995V1GM5ozVw6lW3Zto0sz7l/m92OV0aIT8MiN8jTnimyI+JY84pE0UwYxvB9n6GN4aHQuGnaZM476Tj8HIUxmuodpvlAU/e8h3VdoSgKlGWJsrzpDJVtvb6+svvz8zOWy+Wdp+UTAj/AcaDF/o6+75EXGYoinympb9fz1Zg+ZK3Xa14PMqUSSelutVrx2ZgGTVvzIteVQZGNKLLhD47QOkcYBvw+95CaeYN2iwK3Hmql+X/WelaVZFBJCqU+NUkU4jhm2JAypL65rstImrQQ3GTaxb8J/0PEZvSn/AbimIa8cuOdOAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianDirectoryStructure2021.png&quot;
        title=&quot;ObsidianDirectoryStructure2021.png&quot;
        src=&quot;/static/aa9cafa6bf9fc3f058d98289674c84eb/fcda8/ObsidianDirectoryStructure2021.png&quot;
        srcset=&quot;/static/aa9cafa6bf9fc3f058d98289674c84eb/12f09/ObsidianDirectoryStructure2021.png 148w,
/static/aa9cafa6bf9fc3f058d98289674c84eb/e4a3f/ObsidianDirectoryStructure2021.png 295w,
/static/aa9cafa6bf9fc3f058d98289674c84eb/fcda8/ObsidianDirectoryStructure2021.png 590w,
/static/aa9cafa6bf9fc3f058d98289674c84eb/efc66/ObsidianDirectoryStructure2021.png 885w,
/static/aa9cafa6bf9fc3f058d98289674c84eb/c83ae/ObsidianDirectoryStructure2021.png 1180w,
/static/aa9cafa6bf9fc3f058d98289674c84eb/c4842/ObsidianDirectoryStructure2021.png 1634w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Articles&lt;/strong&gt; - where I store my literature notes (explained later). I named it “articles” since it was shorter, which makes it easier to add a note via Obsidian’s quick switcher:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d27c69581be3221624c949cbd556b840/6b95e/ObsidianQuickSwitcherCreateFile.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 27.027027027027025%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA00lEQVQY02WOa26EMAyEOUNLnISFvAjNQiDdrtT7H20qe5VKVX98GtuyZzxomhBjRkoZ3kcEn5BixjTNUMqAyEKTFf2LEZTSGEcC0YT3txGDMTec5yeez29c10Pq1r6Q1w9obWHNDbzzT+0sOBcRQoJbIkq5YyBSCCGglCKs64rjOLDvO5ZlEZxzot77395aC2OM3OacEeNLh3mepWAjJqUkZrVWtNZwXZfAITzrvEyi7LNyCD80EJEMeGHbNvmCjXtyP7jvuwSd5yl61CohPaw/8gP4qYpt4SNnzQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianQuickSwitcherCreateFile.png&quot;
        title=&quot;ObsidianQuickSwitcherCreateFile.png&quot;
        src=&quot;/static/d27c69581be3221624c949cbd556b840/fcda8/ObsidianQuickSwitcherCreateFile.png&quot;
        srcset=&quot;/static/d27c69581be3221624c949cbd556b840/12f09/ObsidianQuickSwitcherCreateFile.png 148w,
/static/d27c69581be3221624c949cbd556b840/e4a3f/ObsidianQuickSwitcherCreateFile.png 295w,
/static/d27c69581be3221624c949cbd556b840/fcda8/ObsidianQuickSwitcherCreateFile.png 590w,
/static/d27c69581be3221624c949cbd556b840/efc66/ObsidianQuickSwitcherCreateFile.png 885w,
/static/d27c69581be3221624c949cbd556b840/c83ae/ObsidianQuickSwitcherCreateFile.png 1180w,
/static/d27c69581be3221624c949cbd556b840/6b95e/ObsidianQuickSwitcherCreateFile.png 1458w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Files&lt;/strong&gt; - let me state the obvious that files go here, it just makes my root directory cleaner. I don’t bother with subfolders, relying on the backlinks (connections) to find stuff. There’s a useful setting to automatically add all pasted files to this folder:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/38874544e3fb9c1f337cf46159c3b101/2d86d/ObsidianAttachmentFolderSetting.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 11.486486486486488%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAVUlEQVQI13WMOwpAIQwEPYMk4oeoKOb+N1xJwOIVrxhmi2XCnBN7b6y13GMMMPMvKSXHds4ZvXdHREDECBZSVZxz0FpDKcX9qLV+/IIvan8LxxhBRLhbSzP4Yog+gwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianAttachmentFolderSetting.png&quot;
        title=&quot;ObsidianAttachmentFolderSetting.png&quot;
        src=&quot;/static/38874544e3fb9c1f337cf46159c3b101/fcda8/ObsidianAttachmentFolderSetting.png&quot;
        srcset=&quot;/static/38874544e3fb9c1f337cf46159c3b101/12f09/ObsidianAttachmentFolderSetting.png 148w,
/static/38874544e3fb9c1f337cf46159c3b101/e4a3f/ObsidianAttachmentFolderSetting.png 295w,
/static/38874544e3fb9c1f337cf46159c3b101/fcda8/ObsidianAttachmentFolderSetting.png 590w,
/static/38874544e3fb9c1f337cf46159c3b101/efc66/ObsidianAttachmentFolderSetting.png 885w,
/static/38874544e3fb9c1f337cf46159c3b101/c83ae/ObsidianAttachmentFolderSetting.png 1180w,
/static/38874544e3fb9c1f337cf46159c3b101/2d86d/ObsidianAttachmentFolderSetting.png 2578w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blog&lt;/strong&gt; - is where I record ideas for posts and start drawing connections with the rest of the knowledge base. (Fun aside, this post was brewed 🍺 in Obsidian!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templates&lt;/strong&gt; and &lt;strong&gt;Daily Notes&lt;/strong&gt; are plugin-related, and mostly for what they say (though I still ended up doing most of my journalling in Notion)&lt;/li&gt;
&lt;li&gt;The rest of the folders are mostly there from earlier experiments, I will move the stuff out of there… &lt;em&gt;someday&lt;/em&gt; 🏃.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;All&lt;/em&gt; other notes (“permanent” notes) go into the root directory, yay!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the workflow for this course was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start a literature note in Articles for general note-taking&lt;/li&gt;
&lt;li&gt;While taking literature notes, create placeholder notes (or link to existing notes) for individual concepts that seem useful or I want to go deeper into:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c2ba60165a03a5cc5115b844be25c43b/0d0e4/ObsidianLiteratureNoteExample.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60.13513513513513%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACVklEQVQoz02T2W7aUBCGeYiqNAIbs3jB2zHG2OBDCERJCUsKMYuaXiTc97pSH6Fv/VXnRGlz8WlmNGd+z4zGNdPo4rkRU3lLkV8zTMeaUVaSDSfavsUSu+czTCf6Xd+LqX9uclU3aTbaNK4srJZDLQgCFotbhsMhYRgxGuVMp9eaIi9IkgHj8YSylBTFWOcVQgiknOrcbHZDnhe02x1q/X6f/X7Pw2qlH5WyZLVacTge2Ww2LB8e2O123N3dsd1uOR6PfH9+Zj6fc39/z3q95ump4vX1gud51BzH0Q832y3V4aD99XrD4XCgqg7c3Mz5tttpofP5TFVVHE8nbVUjp9OZWAjq9TqWZVFzXZfHx0cWiwXL5VJ39fXrEiklk8mEPM/Jsow0TfVaRqORRvkKlVcarVbrTVCN/PLywuVyYf+0pzpUupPpVPKlXsdsNml+oNFoaD7GpmlqMSVaM80WhqGCDkbTotm0MIw2bauH1e7wyTQx1NdbLV2git/tf9/4J1rrdLqIeIyIJkRhQZpcI6KSIBozixN+hhHCtmnbNj3H0Yu3XVdbhRrX931s237rUKlmo4y8eNuVlCVFkTPMMm6l5PfshkspOZaSezllEAuSIGAgBEEYos4ujoUWV11qwTAKCcOANB0QxRFxHJEkApEO6AYBaZKwDUMe/IB5r8ePvs+dbXNlGLTUSowPI6tjdJ1Qo/4EhWP7Ou52Xf6Ukl/FmNIPSFwP13F0YduytP2IHlkt3/cKPDfD93JNX/sTAm/ILEkQUYwbBISxII5jXfh+Ju/2nb8Nc3lINlPCLAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianLiteratureNoteExample.png&quot;
        title=&quot;ObsidianLiteratureNoteExample.png&quot;
        src=&quot;/static/c2ba60165a03a5cc5115b844be25c43b/fcda8/ObsidianLiteratureNoteExample.png&quot;
        srcset=&quot;/static/c2ba60165a03a5cc5115b844be25c43b/12f09/ObsidianLiteratureNoteExample.png 148w,
/static/c2ba60165a03a5cc5115b844be25c43b/e4a3f/ObsidianLiteratureNoteExample.png 295w,
/static/c2ba60165a03a5cc5115b844be25c43b/fcda8/ObsidianLiteratureNoteExample.png 590w,
/static/c2ba60165a03a5cc5115b844be25c43b/efc66/ObsidianLiteratureNoteExample.png 885w,
/static/c2ba60165a03a5cc5115b844be25c43b/c83ae/ObsidianLiteratureNoteExample.png 1180w,
/static/c2ba60165a03a5cc5115b844be25c43b/0d0e4/ObsidianLiteratureNoteExample.png 1230w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;When I want to internalise something (happens often after failing a weekly assignment 😆), I create a question and sync Anki flashcards via this &lt;a href=&quot;https://github.com/debanjandhar12/Obsidian-Anki-Sync&quot;&gt;plugin&lt;/a&gt; (having a bit of issues rendering Latex though):
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 37.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA40lEQVQoz42RSZKFMAxDuQjzPEMI0/0P5l9P/WHVVd0LlZ1YFlII8jy3siytbVvruk61rmuhaRqh73vNhmF4eyqADweNOI4tmOfZxnEUmX6aJp2XZdEZ4aqqBJaozwc5Z1kmIZAkiQUs7vtu27bZvh92nqf64zh0z1JRFFZ8K4kAQoA7OGma/jjEtvfeVudUnXNyhmPEAeLuO7/vW3XzXimYwcF5FEUWEO88L5EYrusqMd6FnjcKw/CN9RsQeiMjeF2X4lGJiyBCuOq6XguQ/4Ii8wY88POHcUbPHbOH+B9BHH4A1TfOGg3B27AAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianAnkiExample.png&quot;
        title=&quot;ObsidianAnkiExample.png&quot;
        src=&quot;/static/6e582424c5d98fdc34fe53ae98d5d83e/fcda8/ObsidianAnkiExample.png&quot;
        srcset=&quot;/static/6e582424c5d98fdc34fe53ae98d5d83e/12f09/ObsidianAnkiExample.png 148w,
/static/6e582424c5d98fdc34fe53ae98d5d83e/e4a3f/ObsidianAnkiExample.png 295w,
/static/6e582424c5d98fdc34fe53ae98d5d83e/fcda8/ObsidianAnkiExample.png 590w,
/static/6e582424c5d98fdc34fe53ae98d5d83e/efc66/ObsidianAnkiExample.png 885w,
/static/6e582424c5d98fdc34fe53ae98d5d83e/fbf08/ObsidianAnkiExample.png 962w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Occasionally I’ll revise the flashcards, either via the Anki desktop app or the web app on mobile (good enough 🤷‍♂️ there are other options)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;2-the-context&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-the-context&quot; aria-label=&quot;2 the context permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. The Context&lt;/h2&gt;
&lt;p&gt;Next I’ll go deeper into my influences.&lt;/p&gt;
&lt;h3 id=&quot;zettelkasten&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#zettelkasten&quot; aria-label=&quot;zettelkasten permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Zettelkasten&lt;/h3&gt;
&lt;p&gt;This is a system of note-taking often mentioned together with Niklas Luhmann, who had a massive knowledge base (some say &lt;a href=&quot;https://en.wikipedia.org/wiki/Niklas_Luhmann#Note-taking_system_(Zettelkasten)&quot;&gt;90,000 index cards&lt;/a&gt;) and credited the system for helping him write prolifically. He wrote more than 70 academic books and nearly 400 scholarly articles! (Update: I did a deeper dive on Zettelkasten &lt;a href=&quot;https://wasabigeek.com/blog/zettelkasten-and-how-to-take-smart-notes/&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;After multiple failed attempts at starting a hierarchical knowledge base, I’ve found elements of this approach &lt;em&gt;freeing&lt;/em&gt;. Previously, I often got stymied by categorisation - what title should I give this note so it’s searchable? Which folder should it go into? Should I be using a tag instead?&lt;/p&gt;
&lt;p&gt;Now much of this pain is gone, as I eschew traditional hierarchy (deeply nested folders), favouring smaller, “atomic” notes with connections between them (graphs). I think this maps a lot closer to the brain - after all, we draw metaphors or parallels from other disciplines, and may find ourselves often having to recategorise information (especially when learning something new).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3b4b71819c4c1d09454e7c3a197bfc74/11a8f/ObsidianGraphView.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.4054054054054%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABQ0lEQVQoz11S7W6DMAzMU6yIkC9IgHVU69q1U/ch3v+pbnLKRWl/nAz2+ewzqE4bHJZ3/Hz/4Xb7xX5/QNO00NpAtxW0eci1BR06bWGMhzUeypqA1/mAaXyDNT28iwg+PaAPY0GdG/ppi1s+JCitNc7nEy6XT+x2L2jbFs67jJQiYhwyQvBwzm5w8L5C8Lk+DD2UMRan0xnrumaxruvQ9z2GYUBK6SF6L6KEQwghcyUvkHdlrcXx+IHr9QtN02TBGGOGkCQKUQSlWYTkmYMEUidHUf0+3ZUiyfM8l40lCjiwdiIQDSWJuhhTys+06bwvG8lA1qSHUWqyRBEU4jOZQ4Totu3JociyLBjHMeemabpbrkVkAglser4Rc8Kj1bS5kp58Q9rlR6hPwHvyhuRwew7m6YplNlKIYvw16KDelgMkcuN/JXM0DDpMOtoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;ObsidianGraphView.png&quot;
        title=&quot;ObsidianGraphView.png&quot;
        src=&quot;/static/3b4b71819c4c1d09454e7c3a197bfc74/fcda8/ObsidianGraphView.png&quot;
        srcset=&quot;/static/3b4b71819c4c1d09454e7c3a197bfc74/12f09/ObsidianGraphView.png 148w,
/static/3b4b71819c4c1d09454e7c3a197bfc74/e4a3f/ObsidianGraphView.png 295w,
/static/3b4b71819c4c1d09454e7c3a197bfc74/fcda8/ObsidianGraphView.png 590w,
/static/3b4b71819c4c1d09454e7c3a197bfc74/efc66/ObsidianGraphView.png 885w,
/static/3b4b71819c4c1d09454e7c3a197bfc74/c83ae/ObsidianGraphView.png 1180w,
/static/3b4b71819c4c1d09454e7c3a197bfc74/11a8f/ObsidianGraphView.png 1272w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;A related workflow I’ve adopted is a “pre-processing” step of first taking “literature notes” before converting to “permanent” notes.&lt;/p&gt;
&lt;h3 id=&quot;tool-of-choice-obsidian&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tool-of-choice-obsidian&quot; aria-label=&quot;tool of choice obsidian permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tool of Choice: Obsidian&lt;/h3&gt;
&lt;p&gt;I first attempted to maintain a Zettelkasten knowledge base in &lt;a href=&quot;https://www.notion.com&quot;&gt;Notion&lt;/a&gt;, because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One-app-to-rule-them-all: I was already using it as a goal setting and project management tool.&lt;/li&gt;
&lt;li&gt;It had a mobile app, web clipper and a pleasant UX.&lt;/li&gt;
&lt;li&gt;You could (tediously) link various pages together, and they later introduced backlinks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, I moved over to &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;, which had less polish but was better in one crucial aspect; it was easier to adopt a Zettelkasten-influenced system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building connections between notes was trivial (two square brackets), while it took a few seconds to just to search for or create a new note in Notion.&lt;/li&gt;
&lt;li&gt;Searching for a note was much faster, because it’s local first and there is a keyboard shortcut to search by title (Notion defaults to including page text, which slows down search considerably).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What Obsidian also had going for it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Notes are stored as markdown files; which I thought might be easier to work with if I ever wanted my knowledge base to be public (i.e. a digital garden). My blog also uses markdown files, which made conversion pretty straightforward and automatable.&lt;/li&gt;
&lt;li&gt;has a burgeoning plugin system, in particular I wanted to be able to do spaced repetition (see next section).&lt;/li&gt;
&lt;li&gt;Local-first (fast, private) and E2E encryption if you pay for their sync service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other tools I’ve tried:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://roamresearch.com/&quot;&gt;Roam&lt;/a&gt; - arguably one of the first movers in “graph” knowledge bases, but was clunky when I first tried it plus it got expensive.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.remnote.com/&quot;&gt;Remnote&lt;/a&gt; - has built in spaced repetition (see next section) but I found setting up repetition to be a bit unintuitive, and it had no mobile app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;learning-how-to-learn-coursera&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#learning-how-to-learn-coursera&quot; aria-label=&quot;learning how to learn coursera permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Learning How to Learn (Coursera)&lt;/h3&gt;
&lt;p&gt;This was a &lt;a href=&quot;https://www.coursera.org/learn/learning-how-to-learn&quot;&gt;Coursera course&lt;/a&gt; I audited last year, and while it felt a bit disjointed, I had a lot of useful takeaways, in particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;not&lt;/em&gt; actively thinking about a problem can actually help you come up with solutions (diffuse thinking)&lt;/li&gt;
&lt;li&gt;testing yourself + space repetition is &lt;strong&gt;super effective&lt;/strong&gt;, better than blindly re-reading notes etc.&lt;/li&gt;
&lt;li&gt;use memory techniques to help understand and remember: chunking, analogies and metaphors&lt;/li&gt;
&lt;li&gt;exercise and sleep help you think better&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How I’ve tried to apply this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;learnt to appreciate doing “nothing”, especially when working on a problem requiring creative thinking&lt;/li&gt;
&lt;li&gt;wrote questions and used an Obsidian plugin to sync them to &lt;a href=&quot;https://apps.ankiweb.net/&quot;&gt;Anki&lt;/a&gt; (a spaced repetition tool), so I could test myself with spaced repetition. (The “tried” qualifier is important, as in practice this has required quite a lot of effort and discipline 😊)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;growth-mindset&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#growth-mindset&quot; aria-label=&quot;growth mindset permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Growth Mindset&lt;/h3&gt;
&lt;p&gt;This doesn’t manifest in the workflow or the tools I use, but it’s had a massive influence in my learning journey last year. I had watched short videos on having a growth mindset before, but it was only after reading &lt;a href=&quot;https://play.google.com/store/books/details/Mindset_The_New_Psychology_of_Success?id=fdjqz0TPL2wC&quot;&gt;Carol Dweck’s book&lt;/a&gt; that I started internalising it.&lt;/p&gt;
&lt;p&gt;Self-doubt has been a constant struggle for me the past few years, and it sometimes manifests as bouts of “why bother if I’m not cut out for it”, i.e. a fixed mindset. Going deeper in the topic via the book has helped me become more aware of my fixed mindsets, and enabled me to action on it. I regret not reading it earlier.&lt;/p&gt;
&lt;p&gt;The fixed mindset reared it’s head a few times over the course. At the beginning, I had contemplated giving up after struggling to understand some big-Oh proofs. Most recently, I struggled with self-confidence after failing the weekly problem assignment. Thankfully, I stuck with it, mostly on the thinking of “well, at least I’ll learn something”. There was some personal vindication when I realised I was able to solve all of the questions in the final exam (albeit I still had to take it twice, due to some misreading and miscalculation).&lt;/p&gt;
&lt;p&gt;It’s not a perfect book by any means - I’ve found it repetitive and generalising at times - but it’s been more effective at helping me apply the concepts than watching explainer videos or reading articles.&lt;/p&gt;
&lt;h2 id=&quot;3-cautious-optimism&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-cautious-optimism&quot; aria-label=&quot;3 cautious optimism permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Cautious Optimism&lt;/h2&gt;
&lt;p&gt;My knowledge base is still pretty small, it will be interesting to see whether this still holds up over time, especially as I start taking more courses and learning more things at work. I’m also not very disciplined with regularly going through the base to build connections, but I think that’s also part of it’s beauty - minimal process and mental overheads. Let’s see how the rest of the year pans out!&lt;/p&gt;
&lt;p&gt;What’s your learning workflow?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[TIL: Extending a Ruby Struct]]></title><description><![CDATA[Last week I had a head-scratching moment looking at the following code (from the google-api-ruby-client): My initial confusion stemmed from…]]></description><link>https://www.wasabigeek.com/blog/til-extending-a-ruby-struct/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/til-extending-a-ruby-struct/</guid><pubDate>Sun, 07 Nov 2021 10:48:03 GMT</pubDate><content:encoded>&lt;p&gt;Last week I had a head-scratching moment looking at the following code (from the &lt;a href=&quot;https://github.com/googleapis/google-api-ruby-client/blob/google-apis-core/v0.4.1/google-apis-core/lib/google/apis/options.rb&quot;&gt;google-api-ruby-client&lt;/a&gt;):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;ClientOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Struct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:application_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:application_version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:proxy_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:open_timeout_sec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:read_timeout_sec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:send_timeout_sec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:log_http_requests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;:transparent_gzip_decompression&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientOptions&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;default&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@options&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;My initial confusion stemmed from the assumption that the first &lt;code class=&quot;language-text&quot;&gt;ClientOptions&lt;/code&gt; declaration returned a regular object. A helpful colleague explained to me that &lt;code class=&quot;language-text&quot;&gt;Struct.new&lt;/code&gt; actually returns a subclass of &lt;a href=&quot;https://ruby-doc.org/core-3.0.2/Struct.html&quot;&gt;Struct&lt;/a&gt;. That meant the second &lt;code class=&quot;language-text&quot;&gt;class ClientOptions&lt;/code&gt; declaration was actually &lt;em&gt;re-opening&lt;/em&gt; the class to add a method, not to declare a new class.&lt;/p&gt;
&lt;p&gt;Why would one want to do this? Well, one would get all the methods of a regular Struct (e.g. accessors, iterators) &lt;s&gt;for free&lt;/s&gt; at the cost of the inheritance hierarchy. This could be useful for &lt;a href=&quot;https://martinfowler.com/bliki/ValueObject.html&quot;&gt;ValueObjects&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Meta-Postgres: What constraints does a table have?]]></title><description><![CDATA[TIL that checking the structure of your database is just another query away! I needed to find whether a CHECK constraint existed and learnt…]]></description><link>https://www.wasabigeek.com/blog/meta-postgres-what-constraints-does-a-table-have/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/meta-postgres-what-constraints-does-a-table-have/</guid><pubDate>Sun, 16 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TIL that checking the structure of your database is just another query away! I needed to find whether a CHECK constraint existed and learnt that Postgres has &lt;a href=&quot;https://www.postgresql.org/docs/current/information-schema.html&quot;&gt;information_schema&lt;/a&gt; views and &lt;a href=&quot;https://www.postgresql.org/docs/current/catalogs.html&quot;&gt;system catalogs&lt;/a&gt; which you can query like regular tables.&lt;/p&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;information_schema&lt;/code&gt; is actually defined by the SQL standard, so other SQL-compliant databases should have them too. I tried looking first at &lt;a href=&quot;https://www.postgresql.org/docs/current/infoschema-columns.html&quot;&gt;columns&lt;/a&gt;, then &lt;a href=&quot;https://www.postgresql.org/docs/current/infoschema-constraint-column-usage.html&quot;&gt;constraint_column_usage&lt;/a&gt;. Neither had quite what I was looking for, as they did not include the constraint definition.&lt;/p&gt;
&lt;p&gt;Then I found out that each &lt;code class=&quot;language-text&quot;&gt;information_schema&lt;/code&gt; is actually just a view based upon Postgres’ &lt;a href=&quot;https://postgresql.org/docs/13/interactive/catalogs-overview.html&quot;&gt;system catalog&lt;/a&gt; tables 🤯 For example, check out the SQL for &lt;a href=&quot;https://github.com/postgres/postgres/blob/272d82ec6febb97ab25fd7c67e9c84f4660b16ac/src/backend/catalog/information_schema.sql#L801&quot;&gt;constraint_column_usage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This meant I had the building blocks to make exactly what I needed! The &lt;code class=&quot;language-text&quot;&gt;pg_node_tree&lt;/code&gt; column contains a representation of the CHECK definition, which can be parsed with &lt;code class=&quot;language-text&quot;&gt;pg_get_constraintdef&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_get_constraintdef&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;oid
  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;   pg_constraint c
  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;  c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conrelid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;my_table&apos;&lt;/span&gt;::regclass
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(Note to self: the additional parentheses is to evaluate the select result as a value expression / scalar, as opposed to a table.)&lt;/p&gt;
&lt;p&gt;That would give you a result like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pg_get_constraintdef&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class=&quot;language-text&quot;&gt;CHECK ((description &amp;lt;&gt; &apos;&apos;::text))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;appendix&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#appendix&quot; aria-label=&quot;appendix permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Appendix&lt;/h2&gt;
&lt;h3 id=&quot;pro-tips&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pro-tips&quot; aria-label=&quot;pro tips permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pro-Tips&lt;/h3&gt;
&lt;p&gt;Twitter user &lt;a href=&quot;https://twitter.com/ascherbaum&quot;&gt;ascherbaum&lt;/a&gt; has pointed out that when using &lt;code class=&quot;language-text&quot;&gt;psql&lt;/code&gt;, you can use &lt;code class=&quot;language-text&quot;&gt;-e -E&lt;/code&gt; arguments to show the underlying SQL which Postgres uses. Pretty neat!&lt;/p&gt;
&lt;h3 id=&quot;follow-up-questions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#follow-up-questions&quot; aria-label=&quot;follow up questions permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Follow-up Questions&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Are the catalogs stored like any other table, or is there some magic that makes them table-like?&lt;/li&gt;
&lt;li&gt;Does pg_get_constraintdef work with other types of constraints? The docs suggest not.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Don’t give up on your Rails Generators - customise them instead!]]></title><description><![CDATA[Many Rails devs I’ve spoken to quickly abandon the built-in generators. The common reason? Generated files no longer suit the project, so it…]]></description><link>https://www.wasabigeek.com/blog/dont-give-up-on-your-rails-generators/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/dont-give-up-on-your-rails-generators/</guid><pubDate>Sun, 14 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many Rails devs I’ve spoken to quickly abandon the built-in generators. The common reason? Generated files no longer suit the project, so it’s easier to start from scratch or copy and paste. This is a pity, as generators can &lt;strong&gt;improve team productivity&lt;/strong&gt; and &lt;strong&gt;encourage consistency&lt;/strong&gt;, and it’s actually quite straightforward to modify them.&lt;/p&gt;
&lt;p&gt;The Rails docs cover customising generators, but it can feel like a lot to understand if all you want is to make some small tweaks. An easy approach is to copy and override the existing generator, which I’ll walk through in this post.&lt;/p&gt;
&lt;h2 id=&quot;tldr-on-overriding-rails-generators&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr-on-overriding-rails-generators&quot; aria-label=&quot;tldr on overriding rails generators permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TL;DR on overriding Rails Generators&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Copy over the Generator classes to &lt;code class=&quot;language-text&quot;&gt;/lib/generators/...&lt;/code&gt; and modify&lt;/li&gt;
&lt;li&gt;Copy over the template file to &lt;code class=&quot;language-text&quot;&gt;/lib/templates/...&lt;/code&gt; and modify&lt;/li&gt;
&lt;li&gt;Bonus: Use &lt;code class=&quot;language-text&quot;&gt;hook_for&lt;/code&gt; to generate a test file if the generator doesn’t do it automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;background&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#background&quot; aria-label=&quot;background permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Background&lt;/h2&gt;
&lt;p&gt;At work, we use rake tasks quite a fair bit, both for cron-like jobs and one-off migration or support tasks. Rails has a built-in &lt;a href=&quot;https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/USAGE&quot;&gt;task generator&lt;/a&gt;, with the form:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bin/rails g task file_name &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;action&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s never quite worked for us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We like to keep one task “action” per file, with the file named the same as the action - this makes it easier to grep for the task. The built-in generator requires separate &lt;code class=&quot;language-text&quot;&gt;file_name&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;action&lt;/code&gt; arguments, which is a little unnecessary.&lt;/li&gt;
&lt;li&gt;We sometimes namespace tasks e.g. with a “onetime” namespace for one-off tasks, but the built-in generator ignores paths in the &lt;code class=&quot;language-text&quot;&gt;file_name&lt;/code&gt; and also uses the &lt;code class=&quot;language-text&quot;&gt;file_name&lt;/code&gt; as the namespace.&lt;/li&gt;
&lt;li&gt;We write tests for our rake tasks, but the generator doesn’t create test files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s address these! We chose to override the built-in generator entirely, but you could also create a new generator instead.&lt;/p&gt;
&lt;h2 id=&quot;overriding-the-task-generator&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#overriding-the-task-generator&quot; aria-label=&quot;overriding the task generator permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Overriding the Task Generator&lt;/h2&gt;
&lt;p&gt;Rails searches for generators in a &lt;a href=&quot;https://guides.rubyonrails.org/generators.html#generators-lookup&quot;&gt;few paths&lt;/a&gt;, so as long as we place our file in &lt;code class=&quot;language-text&quot;&gt;/lib/generators/task/task_generator.rb&lt;/code&gt;, Rails will load our custom file instead. (Note: if you wanted to create a new generator instead, rename the files and classes according to the lookup logic above!)&lt;/p&gt;
&lt;p&gt;To start, copy over the original task generator to use as a base. Most of the default generators can be found in &lt;code class=&quot;language-text&quot;&gt;railties/lib/rails/generators/rails&lt;/code&gt;, the one we’re after is &lt;a href=&quot;https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/task_generator.rb&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Rails&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Generators&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TaskGenerator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; NamedBase &lt;span class=&quot;token comment&quot;&gt;# :nodoc:&lt;/span&gt;
      argument &lt;span class=&quot;token symbol&quot;&gt;:actions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;banner&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;action action&quot;&lt;/span&gt;&lt;/span&gt;
    
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;create_task_files&lt;/span&gt;&lt;/span&gt;
        template &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;task.rb&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;lib/tasks&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;file_name&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.rake&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Copy it over to &lt;code class=&quot;language-text&quot;&gt;/lib/rails/generators/task/task_generator.rb&lt;/code&gt; in your project. As a simple sanity check to see if we’ve overridden the class, let’s make the generator spit out a hardcoded file name. Change this line:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;template &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;task.rb&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;lib/tasks&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hardcoded_file_name.rake&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run the command:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bin/rails g task path/to/my_task
&lt;span class=&quot;token comment&quot;&gt;# create lib/tasks/hardcoded_file_name.rake&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It works! Revert the hardcoding and leave the rest of the class as-is for now. We’ll be back, promise.&lt;/p&gt;
&lt;h2 id=&quot;overriding-the-generator-template&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#overriding-the-generator-template&quot; aria-label=&quot;overriding the generator template permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Overriding the Generator Template&lt;/h2&gt;
&lt;p&gt;We want a custom template as well, so let’s override that. When invoked, generators search for templates in a different set of paths from the invoker (I explored this more in another &lt;a href=&quot;https://wasabigeek.com/blog/customising-generator-templates-in-rails-gems-too/&quot;&gt;post&lt;/a&gt;, this works for templates from gems as well!) - we’ll be wanting to place our template at &lt;code class=&quot;language-text&quot;&gt;/lib/templates/rails/task/task.rb.tt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Copy the &lt;a href=&quot;https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/templates/task.rb.tt&quot;&gt;original&lt;/a&gt; and modify it as such - the lack of indentation is intentional:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;% class_path.each_with_index do |path_fragment, index| -%&gt;
&amp;lt;%= indent(&quot;namespace :#{path_fragment}&quot;, index * 2) %&gt; do
&amp;lt;% end -%&gt;
&amp;lt;% content = capture do -%&gt;
desc &quot;TODO&quot;
task &amp;lt;%= file_name %&gt;: :environment do
end
&amp;lt;% end -%&gt;
&amp;lt;%= indent(content, class_path.size * 2) %&gt;
&amp;lt;% (0...class_path.size).reverse_each do |index| -%&gt;
&amp;lt;%= indent(&apos;end&apos;, index * 2) %&gt;
&amp;lt;% end -%&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s breakdown the changes. The outer tags implement our desired name-spacing, including the indentation (note that &lt;code class=&quot;language-text&quot;&gt;indent&lt;/code&gt; is actually a private method on &lt;code class=&quot;language-text&quot;&gt;Rails::Generators::Base&lt;/code&gt;, so you won’t be able to use it in your regular view templates):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;% class_path.each_with_index do |path_fragment, index| -%&gt;
&amp;lt;%= indent(&quot;namespace :#{path_fragment}&quot;, index * 2) %&gt; do
&amp;lt;% end -%&gt;
...
&amp;lt;% (0...class_path.size).reverse_each do |index| -%&gt;
&amp;lt;%= indent(&apos;end&apos;, index * 2) %&gt;
&amp;lt;% end -%&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the previous command &lt;code class=&quot;language-text&quot;&gt;bin/rails g task path/to/my_task&lt;/code&gt;, the above namespaces the task correctly:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# /lib/tasks/my_task.rake&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# file is in the wrong location, but we&apos;ll sort this out later&lt;/span&gt;
namespace &lt;span class=&quot;token symbol&quot;&gt;:path&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  namespace &lt;span class=&quot;token symbol&quot;&gt;:to&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The enclosed tags and code are for the actual task:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;...
&amp;lt;% content = capture do -%&gt;
desc &quot;TODO&quot;
task &amp;lt;%= file_name %&gt;: :environment do
end
&amp;lt;% end -%&gt;
&amp;lt;%= indent(content, class_path.size * 2) %&gt;
...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, we indent the task description strings by first assigning it to a variable, &lt;code class=&quot;language-text&quot;&gt;content&lt;/code&gt; via the &lt;code class=&quot;language-text&quot;&gt;capture&lt;/code&gt; helper. Then, we indent and insert it into the file. So running the command again will give us:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;namespace &lt;span class=&quot;token symbol&quot;&gt;:path&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  namespace &lt;span class=&quot;token symbol&quot;&gt;:to&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    desc &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TODO&quot;&lt;/span&gt;&lt;/span&gt;
    task my_task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our file is still being created as &lt;code class=&quot;language-text&quot;&gt;/lib/tasks/my_task.rake&lt;/code&gt; though, instead of &lt;code class=&quot;language-text&quot;&gt;/lib/tasks/path/to/my_task.rake&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-generated-path&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#fixing-the-generated-path&quot; aria-label=&quot;fixing the generated path permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Fixing the Generated Path&lt;/h2&gt;
&lt;p&gt;To fix the path, we make some changes to the generator:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;diff&quot;&gt;&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token coord&quot;&gt;4,5d3&lt;/span&gt;
&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt; argument :actions, type: :array, default: [], banner: &quot;action action&quot;
&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;token coord&quot;&gt;7c5&lt;/span&gt;
&lt;span class=&quot;token deleted-arrow deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;&lt;&lt;/span&gt; template &quot;task.rb&quot;, File.join(&quot;lib/tasks&quot;, &quot;#{file_name}.rake&quot;)
&lt;/span&gt;&lt;span class=&quot;token coord&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token inserted-arrow inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;&gt;&lt;/span&gt; template &quot;task.rb&quot;, File.join(&quot;lib/tasks&quot;, class_path, &quot;#{file_name}.rake&quot;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Above, we’ve removed the unnecessary &lt;code class=&quot;language-text&quot;&gt;actions&lt;/code&gt; argument and added the &lt;code class=&quot;language-text&quot;&gt;class_path&lt;/code&gt; to the generated file’s path. Running the command again nests the file in the right folder now.&lt;/p&gt;
&lt;h2 id=&quot;hooking-in-a-test-file&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#hooking-in-a-test-file&quot; aria-label=&quot;hooking in a test file permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hooking in a Test file&lt;/h2&gt;
&lt;p&gt;“Should you test rake tasks?”… is not a discussion for this article, let’s assume you do! The built-in generator doesn’t create a test file, but we can use &lt;a href=&quot;https://api.rubyonrails.org/v6.1.3/classes/Rails/Generators/Base.html#method-c-hook_for&quot;&gt;hook_for&lt;/a&gt;, which lets the generator invoke other generators. Add the following DSL to your generator class:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;hook_for &lt;span class=&quot;token symbol&quot;&gt;:test_framework&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This attempts to invoke &lt;code class=&quot;language-text&quot;&gt;Rspec::Generators::TaskGenerator&lt;/code&gt;, which… doesn’t exist yet. Let’s resolve that.&lt;/p&gt;
&lt;h2 id=&quot;adding-rspec-generator-and-template&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#adding-rspec-generator-and-template&quot; aria-label=&quot;adding rspec generator and template permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding RSpec Generator and Template&lt;/h2&gt;
&lt;p&gt;RSpec doesn’t have a custom generator for tasks, but the steps for overriding it are more or less the same. Let’s start by creating the TaskGenerator class in &lt;code class=&quot;language-text&quot;&gt;/lib/generators/rspec/task/task_generator.rb&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# frozen_string_literal: true&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;generators/rspec&apos;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Rspec&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Generators&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TaskGenerator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; Base &lt;span class=&quot;token comment&quot;&gt;# :nodoc:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;create_task_specs&lt;/span&gt;&lt;/span&gt;
        template &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;task_spec.rb&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spec/tasks&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; class_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;file_name&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;_rake_spec.rb&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next, we need to create the corresponding template in &lt;code class=&quot;language-text&quot;&gt;/lib/templates/rspec/task/task_spec.rb.tt&lt;/code&gt; (we can definitely DRY this out more, you might be able to draw some inspiration from these examples by &lt;a href=&quot;https://thoughtbot.com/blog/test-rake-tasks-like-a-boss&quot;&gt;Thoughtbot&lt;/a&gt; and &lt;a href=&quot;https://blog.10pines.com/2019/01/14/testing-rake-tasks/&quot;&gt;10Pines&lt;/a&gt;):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;rails_helper&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;rake&apos;&lt;/span&gt;&lt;/span&gt;

describe &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;lt;%= class_path.join(&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;) %&gt;&apos;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  describe &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:&amp;lt;%= file_name %&gt;&apos;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    before&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      Rake&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;define_task&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      Rake&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;application &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rake &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Rake&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
      rake&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rake_require &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tasks/&amp;lt;%= File.join(class_path, file_name) %&gt;&apos;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@task&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rake&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;&amp;lt;%= class_path.concat([file_name]).join(&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;) %&gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    
    it &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;works&apos;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With that done, running the generator command now creates the files just as we wanted. Neat!&lt;/p&gt;
&lt;h2 id=&quot;go-forth-and-generate&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#go-forth-and-generate&quot; aria-label=&quot;go forth and generate permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Go forth and generate!&lt;/h2&gt;
&lt;p&gt;I hope this has given you some inspiration to give generators a second chance - with a little tweaking, they can be really useful in saving time for you and your team. What ideas do you have for improving your generators?&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How does ActiveRecord know the primary_key?]]></title><description><![CDATA[While trying to help someone debug an  error, I went down a rabbit-hole trying to figure out how Rails determines the primary key. Hopefully…]]></description><link>https://www.wasabigeek.com/blog/how-does-activerecord-know-the-primary-key/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/how-does-activerecord-know-the-primary-key/</guid><pubDate>Sat, 13 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While trying to help someone debug an &lt;code class=&quot;language-text&quot;&gt;ActiveRecord::UnknownPrimaryKey&lt;/code&gt; error, I went down a rabbit-hole trying to figure out how Rails determines the primary key. Hopefully this provides a tiny bit more insight on how Rails works!&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;If you’re using Postgres, ActiveRecord searches for a primary key index on the table. If you’re using a different database, the answer probably lies in the respective &lt;code class=&quot;language-text&quot;&gt;SchemaStatements&lt;/code&gt; of the adapter, e.g. &lt;code class=&quot;language-text&quot;&gt;ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements&lt;/code&gt; (&lt;a href=&quot;https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb&quot;&gt;source&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;finding-the-primary-key&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#finding-the-primary-key&quot; aria-label=&quot;finding the primary key permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finding the Primary Key&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ActiveRecord::Base&lt;/code&gt; includes the &lt;code class=&quot;language-text&quot;&gt;AttributeMethods&lt;/code&gt; module, which in turn includes &lt;code class=&quot;language-text&quot;&gt;PrimaryKey&lt;/code&gt;. The module has a &lt;code class=&quot;language-text&quot;&gt;#primary_key&lt;/code&gt; &lt;a href=&quot;https://github.com/rails/rails/blob/main/activerecord/lib/active_record/attribute_methods/primary_key.rb#L70&quot;&gt;method&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;primary_key&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@primary_key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; reset_primary_key &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;defined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@primary_key&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@primary_key&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we follow the &lt;code class=&quot;language-text&quot;&gt;#reset_primary_key&lt;/code&gt; method, we’ll eventually reach &lt;code class=&quot;language-text&quot;&gt;#get_primary_key&lt;/code&gt;, which tries to get the key from the &lt;code class=&quot;language-text&quot;&gt;SchemaCache&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;get_primary_key&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ActiveRecord&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Base &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; table_exists&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
      pk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;schema_cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;primary_keys&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the &lt;a href=&quot;https://github.com/rails/rails/blob/v6.1.2.1/activerecord/lib/active_record/connection_adapters/schema_cache.rb&quot;&gt;SchemaCache&lt;/a&gt;, we see that if the &lt;code class=&quot;language-text&quot;&gt;primary_keys&lt;/code&gt; cache does not already have the key, it will attempt to call &lt;code class=&quot;language-text&quot;&gt;connection.primary_key(table_name)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;primary_keys&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@primary_keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fetch&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# if the table_name key isn&apos;t in the Hash, execute this block&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@primary_keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;deep_deduplicate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; deep_deduplicate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;primary_key&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This method is found in &lt;a href=&quot;https://github.com/rails/rails/blob/v6.1.2.1/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L148&quot;&gt;SchemaStatements&lt;/a&gt;, and it in turn attempts to call &lt;code class=&quot;language-text&quot;&gt;#primary_keys&lt;/code&gt;. This call goes to the database adapter, in my case &lt;a href=&quot;https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L356&quot;&gt;PostgreSQL::SchemaStatements&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;primary_keys&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;table_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# :nodoc:&lt;/span&gt;
  query_values&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SCHEMA&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;SELECT&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attname
      &lt;span class=&quot;token constant&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
             &lt;span class=&quot;token constant&quot;&gt;SELECT&lt;/span&gt;
               &lt;span class=&quot;token constant&quot;&gt;FROM&lt;/span&gt; pg_index
              &lt;span class=&quot;token constant&quot;&gt;WHERE&lt;/span&gt; indrelid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#{quote(quote_table_name(table_name))}::regclass&lt;/span&gt;
                &lt;span class=&quot;token constant&quot;&gt;AND&lt;/span&gt; indisprimary
           &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; i
      &lt;span class=&quot;token constant&quot;&gt;JOIN&lt;/span&gt; pg_attribute a
        &lt;span class=&quot;token constant&quot;&gt;ON&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attrelid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;indrelid
       &lt;span class=&quot;token constant&quot;&gt;AND&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attnum &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;indkey&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;idx&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
     &lt;span class=&quot;token constant&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BY&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;idx
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The subquery in particular is crucial - basically, Rails looks for a primary key index belonging to the table. Let’s focus on the important bits:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* from all the indexes */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_index
&lt;span class=&quot;token comment&quot;&gt;/* find those for table_name */&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* you can google for regclass, it&apos;s another Postgres rabbit-hole */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; indrelid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; table_name::regclass
&lt;span class=&quot;token comment&quot;&gt;/* that are primary keys */&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; indisprimary&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is usually automatically created during a migration - Rails will add an &lt;code class=&quot;language-text&quot;&gt;id&lt;/code&gt; column and make it a primary key. If you have a &lt;code class=&quot;language-text&quot;&gt;structure.sql&lt;/code&gt; file, you’d find statements like &lt;code class=&quot;language-text&quot;&gt;ADD CONSTRAINT table_name_pkey PRIMARY KEY (id)&lt;/code&gt; which do exactly that.&lt;/p&gt;
&lt;h2 id=&quot;bonus-how-did-i-find-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bonus-how-did-i-find-it&quot; aria-label=&quot;bonus how did i find it permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bonus: How did I find it?&lt;/h2&gt;
&lt;p&gt;It took me quite a while to figure this out, I’m sharing my abridged (because I was flailing around a &lt;em&gt;lot&lt;/em&gt;) methodology in hopes that it might be helpful to you when debugging - if you know a better way, do share!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Did a GitHub search in the Rails repo for the UnknownPrimaryKey error. There were a few results that were in the form &lt;code class=&quot;language-text&quot;&gt;finder_class.primary_key&lt;/code&gt;, so I assumed it was a method on the model (and confirmed that by playing around in the rails console).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Did another search for &lt;code class=&quot;language-text&quot;&gt;&quot;def primary_key&quot;&lt;/code&gt; (“primary_key” alone gave too many results), this led to &lt;code class=&quot;language-text&quot;&gt;ActiveRecord::AttributeMethods::PrimaryKey&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wasn’t sure that this was the correct method but I attempted to follow the code through (GitHub’s &lt;a href=&quot;https://docs.github.com/en/github/managing-files-in-a-repository/navigating-code-on-github#jumping-to-the-definition-of-a-function-or-method&quot;&gt;jump to definition&lt;/a&gt; was a godsend) and got a little stuck/distracted by trying to figure out what class &lt;code class=&quot;language-text&quot;&gt;connection&lt;/code&gt; referred to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After a bit of poking around in the console, I recalled seeing this class called &lt;a href=&quot;https://ruby-doc.org/core-2.7.2/TracePoint.html&quot;&gt;TracePoint&lt;/a&gt;, and decided to give it a try:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;trace &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TracePoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;new&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;:raise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;tp&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    p &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;lineno&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;raised_exception&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

trace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;enable&lt;/span&gt; { ExampleModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;first&lt;/span&gt; }&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This gave an extremely long trace 🤯 good thing I could search in my terminal, after grepping for &lt;code class=&quot;language-text&quot;&gt;primary_keys&lt;/code&gt; I came across this set of calls, which confirmed that the method was in &lt;code class=&quot;language-text&quot;&gt;PostgreSQL::SchemaStatements&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;48&lt;/span&gt;, ActiveRecord::ConnectionAdapters::SchemaCache, :primary_keys, :call&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;53&lt;/span&gt;, ActiveRecord::ConnectionAdapters::SchemaCache, :data_source_exists?, :call&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;146&lt;/span&gt;, ActiveRecord::ConnectionAdapters::SchemaStatements, :primary_key, :call&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;355&lt;/span&gt;, ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements, :primary_keys, :call&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content:encoded></item><item><title><![CDATA[Window Functions, Visualised - Rankings]]></title><description><![CDATA[Leading from an introduction to Window Function Calls, let’s dive deeper into the different Window Functions that are available. Today, we…]]></description><link>https://www.wasabigeek.com/blog/window-functions-visualised-rankings/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/window-functions-visualised-rankings/</guid><pubDate>Mon, 04 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Leading from an &lt;a href=&quot;/blog/window-function-calls-an-introduction/&quot;&gt;introduction to Window Function Calls&lt;/a&gt;, let’s dive deeper into the different Window Functions that are available. Today, we’ll look at &lt;code class=&quot;language-text&quot;&gt;row_number&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;rank&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;dense_rank&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;percent_rank&lt;/code&gt;. We’ll be using the same expenses table from the previous post, which had entries like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In the following examples, our &lt;strong&gt;window frame&lt;/strong&gt; will be the whole expenses table, ordered by highest to lowest cost:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; window &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  expenses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  expenses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cost
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; expenses&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/116c3063fe1adc7cbe60400693705952/17d12/window_frame.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.16216216216216%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+ElEQVQoz3WSW4+iQBCF/f+/anYfTJyNWR1xxAsqFxHkDt1yETibUxsmmYeppEOnOf111amaAUDXA1U7QLcD8rpHXg/4KahV1HYDwrJD14/f/s+aukZaamy9Anak4MQKq0uMQtVomwZKKQzDIKtSCqegwMZOYfsRlgcPeztAkWdIkgRN02D2eDxw2O+xN3f4877A7tOAqgqcLQue5yGKIoFSfDwe8L5Y4PevN3ysVwBGedQwDJRFIVABmuYe2+0Wy+USq9UajyiS/fV6RRAE0Frh+XzCcVyYpinLtm0B3O93GJsNfN+X/SxNU+R5Dv92g+u6sCxLvGBmjLIsobWWDPPQwufWwGZjiJZR17VU0nUdkjj+D+Sl8/mMMAxF9Hq9JLNxHKVcRWAZIgkcrNcfOB6P4imD95ktI56AVVWJiAeMvu/huS6GCZg4UEWMstJSDbOago8SzjsCzLJMRPP5XDxjtG0Lx3GAoUMV2yjPb6hufwUojWhbAU1a2kHoV4b0iLDT6SResmQCX3WBKn+gdJdQ/hpZVsCyTtLAqWRWMNnzBeQhTWan2AyK8ywDc6h0DRXuoBIXSj+/DfEEnXwXIAGs/ydhmiYYRkDrp3SRY3a5XCSrKThSzFDmkB2miOBp8SV+CZsushGcAlrBiWBFrIw94J59YLX/ABO5jgdtdQFNAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Original table to window frame&quot;
        title=&quot;Original table to window frame&quot;
        src=&quot;/static/116c3063fe1adc7cbe60400693705952/fcda8/window_frame.png&quot;
        srcset=&quot;/static/116c3063fe1adc7cbe60400693705952/12f09/window_frame.png 148w,
/static/116c3063fe1adc7cbe60400693705952/e4a3f/window_frame.png 295w,
/static/116c3063fe1adc7cbe60400693705952/fcda8/window_frame.png 590w,
/static/116c3063fe1adc7cbe60400693705952/efc66/window_frame.png 885w,
/static/116c3063fe1adc7cbe60400693705952/c83ae/window_frame.png 1180w,
/static/116c3063fe1adc7cbe60400693705952/17d12/window_frame.png 1666w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;row_number&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#row_number&quot; aria-label=&quot;row_number permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;row_number&lt;/h2&gt;
&lt;p&gt;Let’s start with &lt;code class=&quot;language-text&quot;&gt;row_number&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All &lt;code class=&quot;language-text&quot;&gt;row_number&lt;/code&gt; does is add a running number for each row in the frame, starting from 1:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row_number&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;dense_rank&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dense_rank&quot; aria-label=&quot;dense_rank permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;dense_rank&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;dense_rank&lt;/code&gt; is more interesting. Reviewing the previous example, we can see that there were a few entries that have the same cost. The Postgres documentation refers to these as &lt;strong&gt;peer groups&lt;/strong&gt;, and &lt;code class=&quot;language-text&quot;&gt;dense_rank&lt;/code&gt; adds a running number counting by the groups instead of rows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ebdb9248832051f5d8c653f55af4d0e/ce92a/dense_rank.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.02702702702703%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACV0lEQVQ4y22UW3PaMBBG+/9/TtP3NsNLQtJ0BibQkJBgg7F8v8rGWPLpSA6ETqKZnV3Z2qNP0krfAIZhMI44jtntdvi+TxiGNhZCEMeRjV3XJU1TPM9jvX5BCN9+3+/3Nse0b5fAKIosdDqdMp/PuL7+ZQcr1bNYPPLw8MDxeGS1WnF19Z3ZbMZkMmGz2VgBn4AmeWtUBUbRHhGERHFCkmbkRUlZ1uR5QVEUlGVFnueUZWGFGPWfgIVJKjJQEt1Vox1rUC2gYOhGb9vJj/lnhaZzAkrZEAuXzdMfXp8XoBqGrqSXEevFLTJ6QWcrzGhVuuhjM6KVIgyDD4UwAoOsxvE8mmBJ5i3Q1RZd7VC1TxF79G0OvXwfzdkbYBC8A6uD4uatIJGKNK8oI4fYf+P3zTVZuAXd0DcZvvOXvg4ZuuIM4gL436Fs0xY36ygqydZ9oa5KfBFS1fKsxJSLOW1de7ava5+hb87wJEnoum4EPgnJfC/JS4kQHnnosl6/UuTZWYWpubZtQfqYLTd7O6gD79tvJ5RSjkAnaZk6Fa8i55ALyixkuXrD88dlHHtFmuUoPTC0yRkynrD+DOx6jRpgl9RWYRsuebz/yX79AAfBMX1GrO84REuG2htBR/m1wuFiOtlpts4zm+UdztM9t9c/mN9PYDjw9vxoT1mXjh2rmxBt1PLFki9rMfY3FJFLGjiU8Y48dBgOGV0ZoJvYxpftlJdl2ddAXwh6pdBan5NMbP4br/XJK2umZMaHJaKu61Nhf8wkfN++HKZQRxMX8WjmVlz2T3fZVME/fpJ8fV3LF/0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Dense Rank Peer Groups&quot;
        title=&quot;Dense Rank Peer Groups&quot;
        src=&quot;/static/5ebdb9248832051f5d8c653f55af4d0e/fcda8/dense_rank.png&quot;
        srcset=&quot;/static/5ebdb9248832051f5d8c653f55af4d0e/12f09/dense_rank.png 148w,
/static/5ebdb9248832051f5d8c653f55af4d0e/e4a3f/dense_rank.png 295w,
/static/5ebdb9248832051f5d8c653f55af4d0e/fcda8/dense_rank.png 590w,
/static/5ebdb9248832051f5d8c653f55af4d0e/efc66/dense_rank.png 885w,
/static/5ebdb9248832051f5d8c653f55af4d0e/c83ae/dense_rank.png 1180w,
/static/5ebdb9248832051f5d8c653f55af4d0e/ce92a/dense_rank.png 1459w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So changing the SQL to this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; DENSE_RANK&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Gives us this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I’m actually not sure how Postgres orders the rows &lt;em&gt;within&lt;/em&gt; a peer group - in my small example, it looks like an implicit &lt;code class=&quot;language-text&quot;&gt;ORDER BY ID DESC&lt;/code&gt; was added, but the Postgres &lt;a href=&quot;https://www.postgresql.org/docs/current/queries-order.html&quot;&gt;docs&lt;/a&gt; also say for general ordering, &lt;em&gt;if sorting is not chosen, the rows will be returned in an unspecified order&lt;/em&gt; 🤷‍♂️.&lt;/p&gt;
&lt;h2 id=&quot;rank&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rank&quot; aria-label=&quot;rank permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;rank&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;rank&lt;/code&gt; has one big difference from &lt;code class=&quot;language-text&quot;&gt;dense_rank&lt;/code&gt; - it counts the “gaps” in the previous peer group:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/16828e5d43e47a6d721fa4d21f043acd/c1b63/rank.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.54054054054053%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC+ElEQVQ4y6WU227bOBCG/f6vshe52VxsDeyim7WLpA0at/ExsWzLkiz5KEWWSEoUv4K0641R7NUOMBgOQf6c+Yczrc1mg+/7rNdrdrsd1rfW+mEYOl2tVkRRdPGtxnHs9qwNggDP86jrmpbdtCCdTofRaMTd3R23t7fusJX9fs/Dw4MDsbLdbrm5ueH+/t7ttdsfEEK4c2VZ0vL9BbPZjPF4RL//zNPTVwaDPv5iQZIk7pI3nbK0WSSJi8Y+PJlM3L3hcOjOvLy8oJSiZZ00y2gMFEJS1Q3agKo0QlZOVVVffCUltW6oa+0AbJpWl8slUkpalgOlJFQH0CnIHUZsnUXsQGfQ5JgygSp3aaPlSd+JxbGpt+JkjSozlpNHut0O29gHXaBFCk3B4rXPZPCEKRL0fuwuN8cY/eaf1k3jrC2cA7TpHvZbqnTBIZ7SlKfonFUp2cYn30eYOsPUhbtsTIMx5rw214BCKuJwTrF5ZTG8Z9TrILMIY9NVB3axx2bWp5n30cfsKk0L9iugqljNvlOVGYvFAn8+pZIlNBU0ijw/sPNniD9/R82fT0C6clH+Z4RJNCddz1klG8bjMW95fonikGYkcULV+4Ic/XPiLQ/R6fQE+I5DV2WXcuSjspAgXBEEtvziApi9ZazCiOr5EfX65RxhjanlVYS2OfI8t4CSOFpS5bGLsD8YsdnuL4DpW0GWHlGfP6G8b++4u+awKArXri0hSpL1jiqdMXzqcPfXH6TbEEwBx5D1vEcw+Yz8+Bvi60eaQmC0viqMFcuf/YstISRx4FGuh+TBI4fFA/PeB4LB3yBi9uGQQzSCMkTnAfJbjzoK0but4+8noKXJ8tiyLRfPvhP6UzrdT64n2+023W7XHVzHIatwCVWGTr1TUaRAea+/ROgAXYWiEHFYgrKfegMmg3pPfYwxtgXlHi0ONLU689ZgzmA/O+UK0OZea8P/ETsgLoBhGDDzPFZ2iAaBG1H2+wTn9b97wWnAvl+HgZuddpRZ+wNKmVVpFSZbogAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Rank Peer Groups&quot;
        title=&quot;Rank Peer Groups&quot;
        src=&quot;/static/16828e5d43e47a6d721fa4d21f043acd/fcda8/rank.png&quot;
        srcset=&quot;/static/16828e5d43e47a6d721fa4d21f043acd/12f09/rank.png 148w,
/static/16828e5d43e47a6d721fa4d21f043acd/e4a3f/rank.png 295w,
/static/16828e5d43e47a6d721fa4d21f043acd/fcda8/rank.png 590w,
/static/16828e5d43e47a6d721fa4d21f043acd/efc66/rank.png 885w,
/static/16828e5d43e47a6d721fa4d21f043acd/c83ae/rank.png 1180w,
/static/16828e5d43e47a6d721fa4d21f043acd/c1b63/rank.png 1200w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Let’s compare the results of each function to show the difference:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;percent_rank&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#percent_rank&quot; aria-label=&quot;percent_rank permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;percent_rank&lt;/h2&gt;
&lt;p&gt;This is an interesting one - how it’s derived is a bit of a mouthful, so let’s show by example, starting with the query:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; PERCENT_RANK&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the result:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;percent_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.11…&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.22…&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.66…&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.77…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.88…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.88…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Instead of a running number, we get the “relative rank” from 0 to 1 inclusive. This is useful if you need to know how close something is to the highest rank - though, as the above example shows, it may not end with 1 if there are ties for the highest rank (a deeper explanation &lt;a href=&quot;https://dba.stackexchange.com/a/144015&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;side-by-side&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#side-by-side&quot; aria-label=&quot;side by side permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;side-by-side&lt;/h2&gt;
&lt;p&gt;Finally, let’s look at results side by side:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row_number&lt;/th&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;th&gt;percent_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.11…&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.22…&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33…&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0.66…&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.77…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0.88…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0.88…&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I hope that helped! Here’s an &lt;a href=&quot;http://sqlfiddle.com/#!17/d3ff0a/8&quot;&gt;sqlfiddle&lt;/a&gt; you can play around with.&lt;/p&gt;
&lt;p&gt;In future articles, we’ll look into more window functions. Follow me on Twitter to be informed of the next one!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Window Function Calls in Postgres - A Visual Introduction]]></title><description><![CDATA[What is a window function call? My explanation: adds a column to query results that is calculated based on other rows in the table Ok, that…]]></description><link>https://www.wasabigeek.com/blog/window-function-calls-an-introduction/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/window-function-calls-an-introduction/</guid><pubDate>Thu, 31 Dec 2020 21:56:03 GMT</pubDate><content:encoded>&lt;p&gt;What is a &lt;strong&gt;window function call&lt;/strong&gt;? My explanation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;adds a column to query results that is calculated based on other rows in the table&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok, that’s probably still not immediately understandable, so let’s walk through an example. Imagine you’ve been tracking expenses in Postgres:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now, say you wanted to see how much each expense compares to the average:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;avg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;17.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; use a subquery to calculate the average:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;avg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; expenses&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; expenses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But we could also be a little fancier and use a window function call:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;avg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;over&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; expenses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Although we have some new syntax to understand (which we’ll get into later), that’s noticeably shorter! What just happened?&lt;/p&gt;
&lt;h2 id=&quot;hello-window-frames&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#hello-window-frames&quot; aria-label=&quot;hello window frames permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hello, Window Frames&lt;/h2&gt;
&lt;p&gt;Let’s try to visualise the window function call. For each row, Postgres calculates the average cost of the rest of the table, which is the default &lt;strong&gt;“window frame”&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1014b18a4b3fbfc949cb36a9403672a2/2bef9/avg_cost_whole_table.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.32432432432432%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAACC0lEQVQoz4VS21LbMBDl//+lj32jT71AKSTAYIc4diCO5Ztk3ezYkixpO3ZgEjqd9owedkd79uwe6cIDeJjgnPfHCMB7MGacjxnH0QNY55TSczYqpay1AHBxrLbOaT3gKmcUFyhtSOWt9lY7a4zRuK6EEN2ha5qmqmtKWdd1E7lTkJSwzkZMu7tg/3MZXy82yyAtqcmwRiVH8dPy+uv6/ga9bFBJovVqvQqEEBOZd7DYQsn81bN5LXpExmQvawEZNohBnWdNme9rTRoRrxOSrkDtR4k4l29je4CbyF3eTzovSDys9hV3JYOMwq5oi13y9KpiNERBIDoNfWZJSHk7kd8N81skUzzmeMBciR56DXoE67wjT8Z6a123vx2UAsNt+qVpyEkZAKRgnWRJHDFGZ8P9m+8sJIQw0R7KVc/Q5K5Im/Tx5DYACCGMMcf6M3igwbFRmwc9TSey6kj87V15vmOMbbfbMAwZY2dcDzTcRFFeEdNsRJUAwKgPePPjT2Xn3GngM+W+PyjjvNxRFM2/YsDxlR7dB7Ke4c9grRvQHRet7N2hCHbPtzUfyOui2D4ae6aslOKcCyH4CUwyPKBFv/veZ79U/FkWq1YKVT54qz+M/XcoAvIFkk+A7yC7BLKAPgcR+3m9f5DnzY2E8gYGAqaFeulp6BXzVh0r/qfsLTQh9PUUjN3U4uwxfwPYyFhU/HYWBQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Calculating average cost over the default window frame&quot;
        title=&quot;Calculating average cost over the default window frame&quot;
        src=&quot;/static/1014b18a4b3fbfc949cb36a9403672a2/fcda8/avg_cost_whole_table.png&quot;
        srcset=&quot;/static/1014b18a4b3fbfc949cb36a9403672a2/12f09/avg_cost_whole_table.png 148w,
/static/1014b18a4b3fbfc949cb36a9403672a2/e4a3f/avg_cost_whole_table.png 295w,
/static/1014b18a4b3fbfc949cb36a9403672a2/fcda8/avg_cost_whole_table.png 590w,
/static/1014b18a4b3fbfc949cb36a9403672a2/efc66/avg_cost_whole_table.png 885w,
/static/1014b18a4b3fbfc949cb36a9403672a2/2bef9/avg_cost_whole_table.png 1024w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now if that was all you could do with window function calls, I’d be disappointed too. Thankfully, it’s not! Aside from being shorter, there are some key features that are harder to replicate with subqueries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;calculations can be done over a subset of the table (i.e. a smaller &lt;strong&gt;window frame&lt;/strong&gt;) instead of the whole table&lt;/li&gt;
&lt;li&gt;in addition to the usual aggregation functions like &lt;code class=&quot;language-text&quot;&gt;avg&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt;, there are &lt;strong&gt;window functions&lt;/strong&gt;, which can only be used as part of a window function call&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;reframing-the-window&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#reframing-the-window&quot; aria-label=&quot;reframing the window permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Reframing the Window&lt;/h2&gt;
&lt;p&gt;Let’s try calculating over a subset. Knowing the average across all expenses wasn’t very helpful - a big purchase could skew the calculation, so let’s instead compare each expense to the &lt;em&gt;category average&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;avg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;over&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; category&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;avg in category&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; expenses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our result now looks something like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;avg in category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi to home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That’s a bit more helpful! Let’s visualise this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6107b4b5b64a472d789accb94501592f/2bef9/avg_cost_category.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.2972972972973%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7ElEQVQoz3WTW3PbIBCF/f//Uh97SWZS59qxU0uqLaxIsi4IkAUIAaeDlKStJ93hgRn2A87Z3RUA7/G/8N4tG6WU1hqAtXaQciFWAPSEXoH34/5Yx4e8bM7aoHwhm4d1vH2k1Quvy6f7h8fbG8FZXZWP62vaVAGWI653uEkQ5/ZY8IS0x0oJIUl6jNLmZ1K8VOeWGznaffyLMwFDbfbF0jjA1qHiiAo8Z5bkLCYsq0cqTE7S9fe7ZBc3VdNWVbpPs+iHnUYYAbpZJK3e5VnrU5LVdeM9nIOz5nSqlFTee+ecktKYoBnmDLr9A3uPxYBpMs7Z+cDBKj+dnWq8aqBbI/KxTeAMvAXfL26u/jLWc877vhf9gKlXTbKPttHmTrEcEzuRXZeuYYeQKlJY/er2Es45xtgwDOdhCC8Mhe0Lp1sYBtV61UI3cNMME0zyH3jhrbXOhdqa7iCKZ5ptebGDqsaOTDSCm2WLQ/j8xcuU0rIsy1MFb8Y2btouSWJKKYBejrqJYHjAWHTp9gKzrqvrGoAsnwhJo10keAd4zsWQ3cKeMXL06SUcACm11uO4/C319b3NPiP/CvIpLJYEsnsOjry354dNPTd0i/IKYo/iG4oryDqonX3+APbziPj3QXEj2g2m2fxQ4ent3teE36FvI2lX/+ssAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Calculating average cost over each category&quot;
        title=&quot;Calculating average cost over each category&quot;
        src=&quot;/static/6107b4b5b64a472d789accb94501592f/fcda8/avg_cost_category.png&quot;
        srcset=&quot;/static/6107b4b5b64a472d789accb94501592f/12f09/avg_cost_category.png 148w,
/static/6107b4b5b64a472d789accb94501592f/e4a3f/avg_cost_category.png 295w,
/static/6107b4b5b64a472d789accb94501592f/fcda8/avg_cost_category.png 590w,
/static/6107b4b5b64a472d789accb94501592f/efc66/avg_cost_category.png 885w,
/static/6107b4b5b64a472d789accb94501592f/2bef9/avg_cost_category.png 1024w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;For each row, our new window frame includes only other rows with the same category, and the average is calculated based on those rows.&lt;/p&gt;
&lt;p&gt;This is also a good time to take a brief look at the syntax we’ve used so far:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;avg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cost&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; category&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;avg(cost)&lt;/code&gt; is the aggregation function that we want to execute over the window frame - we could use other functions such as &lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;min&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;OVER&lt;/code&gt; is the keyword which tells Postgres we were using a window function call. Everything in parentheses specifies how our table will be divided into different window frames&lt;/li&gt;
&lt;li&gt;in this case, we are using &lt;code class=&quot;language-text&quot;&gt;PARTITION BY category&lt;/code&gt;, which means we are dividing the table into different window frames based on their category&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;window-functions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#window-functions&quot; aria-label=&quot;window functions permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Window Functions&lt;/h2&gt;
&lt;p&gt;Now that we know a little more about the syntax, let’s look at the other hidden power of window functions calls… &lt;strong&gt;window functions&lt;/strong&gt; themselves!&lt;/p&gt;
&lt;p&gt;It’s coming to the end of the month, and you want to know the biggest expense in each category, for each day. There’s a window function for that - &lt;code class=&quot;language-text&quot;&gt;rank&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;With &lt;code class=&quot;language-text&quot;&gt;rank&lt;/code&gt;, we can calculate the order of each row in it’s window frame (Note that the following query still returns every row in the table, not just the top expenses - we’ll fix that later):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  rank&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;over&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; created_at
    &lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; expenses&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the additional syntax in our window function call:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; created_at &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; cost &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;we’ve added a second condition to &lt;code class=&quot;language-text&quot;&gt;PARTITION BY&lt;/code&gt; to further divide each window frame by the day each entry was &lt;code class=&quot;language-text&quot;&gt;created_at&lt;/code&gt; (this is very similar to &lt;code class=&quot;language-text&quot;&gt;GROUP BY&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;there’s now an &lt;code class=&quot;language-text&quot;&gt;ORDER BY ... DESC&lt;/code&gt; that orders the rows within each frame from highest to lowest cost, so we’ll know that a rank of 1 would be the most expensive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another quick visual:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c2273fb3c2a5f6a4d8b43e62292fa544/2bef9/rank_category_date.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 48.64864864864865%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcUlEQVQoz42RW1PbMBCF/f//EjNkmE65NASIY6zmYohlR3FiI182kkxk7XYcp9DpC5ynndV+OjorD+lrdc4RkVLKOYeITdNorYnIIyJrj1odqqo0xlDXFuli8/rcyJyv47vr69CfxtECCsHuL6vstYEDC8M0TXvYjyna6HEgRjfze7aP+fb26uJy9HMeMpFwFgRPk0n0IqIExtN1svCNMWbHjFZI5P1gtBQYrpW/kizWItfs4XYyfoxXK7nbCh4ny1DyoEpm5frhWPI+Bp6zeuZItSJC13Vd39c5lqs2X74XS6xedBbCdu7Ultq9THxbRuQ6hPREozdcIoSoyrJvwEbyIHr+BbsFKd7mv0GEruYOUhAzhD4qvddn5wF27uxMzvbm7RsdgdBZa12/6n5Ka2Wt/fcXvIEEgKIopJSIlG52T9NgX7wRkZQyy7KBUUqdDQg/YUQEgLqulVJEZIw+AOBpAk+iv6/7qD/hQf8dfEd/ACYyOMjX6E9NAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Calculating rank over each category and date&quot;
        title=&quot;Calculating rank over each category and date&quot;
        src=&quot;/static/c2273fb3c2a5f6a4d8b43e62292fa544/fcda8/rank_category_date.png&quot;
        srcset=&quot;/static/c2273fb3c2a5f6a4d8b43e62292fa544/12f09/rank_category_date.png 148w,
/static/c2273fb3c2a5f6a4d8b43e62292fa544/e4a3f/rank_category_date.png 295w,
/static/c2273fb3c2a5f6a4d8b43e62292fa544/fcda8/rank_category_date.png 590w,
/static/c2273fb3c2a5f6a4d8b43e62292fa544/efc66/rank_category_date.png 885w,
/static/c2273fb3c2a5f6a4d8b43e62292fa544/2bef9/rank_category_date.png 1024w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So for each row, our window frame is other rows with the same category and created_at date, and we order the rows from highest to lowest cost.&lt;/p&gt;
&lt;p&gt;The query gives us a result like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now that we have a ranking, we could use it as a subquery and filter out expenses that are rank 2 or higher.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;-- our earlier query&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ranked_expenses&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; rank &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which leaves us with exactly what we want - nice!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;#x26; drinks&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi to home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I hoped that helped you grok window functions! If you want to play around with the data in this example, there’s an &lt;a href=&quot;http://sqlfiddle.com/#!17/f33e78/3&quot;&gt;sqlfiddle&lt;/a&gt; - in case that doesn’t exist at the time of reading, you can also get the SQL to create the table and query the data from this &lt;a href=&quot;https://gist.github.com/wasabigeek/2b9fb05eba5c26928bab85bcf408511f&quot;&gt;gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the next few articles, we’ll look into the individual window functions, starting with those for &lt;a href=&quot;/blog/window-functions-visualised-rankings/&quot;&gt;ranking rows&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[2020 in Retrospect: Making]]></title><description><![CDATA[Reflections on side projects this year. Overall I tend to build first (out of technical interest or my own personal problem), then make only…]]></description><link>https://www.wasabigeek.com/blog/2020-in-retrospect-making/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/2020-in-retrospect-making/</guid><pubDate>Fri, 11 Dec 2020 20:48:03 GMT</pubDate><content:encoded>&lt;p&gt;Reflections on side projects this year.&lt;/p&gt;
&lt;h2 id=&quot;overall&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#overall&quot; aria-label=&quot;overall permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Overall&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I tend to build first (out of technical interest or my own personal problem), then make only half-hearted efforts to validate and abandon them quickly.&lt;/strong&gt; There’s two takeaways for me: I should make a proper effort to get better and more comfortable with customer discovery and idea validation, and if it’s to validate an actual business I should do that before building. Will be trying out &lt;a href=&quot;https://twitter.com/PierreDeWulf/status/1329765206256455683?s=20&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://www.indiehackers.com/post/how-do-you-create-a-product-people-want-to-buy-a28b69ebb3&quot;&gt;suggestions&lt;/a&gt; around that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The “technical” projects were great for personal learning, and fun (until they got serious)&lt;/strong&gt;. I still have a lot to learn and catch up in dev, so I think it’s worth setting aside time for these. Maybe a dual-track thing - set aside 50% time to validate ideas, 50% time to improve technically? Or maybe I should try to capitalise on the learnings e.g. teaching others?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I abandoned some projects once it became clear they took more effort than expected, so I didn’t see myself making money from them soon&lt;/strong&gt;. Perhaps in addition to the “dual-track” approach, the ideas I should try to validate first should be more around “simpler, one-time sale” products as &lt;a href=&quot;https://robwalling.com/2015/03/26/the-stairstep-approach-to-bootstrapping/&quot;&gt;Rob Walling suggests&lt;/a&gt;. That might take off some pressure on pure-tech projects so I can go deeper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I get quite discouraged and annoyed with design&lt;/strong&gt;. I am trying to get better at it for my Dec project, but not in a particularly structured manner. I do think it’ll be useful, just not sure if I should be prioritising that over other things.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mar-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mar-2020&quot; aria-label=&quot;mar 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mar 2020&lt;/h2&gt;
&lt;h3 id=&quot;a-self-hosted-kahoot-clone&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-self-hosted-kahoot-clone&quot; aria-label=&quot;a self hosted kahoot clone permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A “self-hosted” Kahoot! clone&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7168019d4007fcce1862971021646b46/1e5d2/cahoots_screenshot.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.567567567567565%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABaklEQVQoz3WRu27cMBBF9f9Ik6QwEKTIB7hxYRcu8gGpUgaO11qJu5LWelBePSmJPAaphzdGfIWBOOTwzr1DL8kK0iwnlyXimCCiE3F84hjGhNEzSSYRQUAQHTieUgopiZOURz9kLyLCpwO7J0EYpwQiwgOYpgmtNcYY7OegNdrg9utCstYNw8A4jkzT6HIzTi63ULLEG8cBKUvK0saL+0speTmfqarKFdbn6q3Rf2CFOMKuxzNG07YdXdehlKLve3qlaJoGpQZXXNf1dsnC1uR54RpfQg1qtaydtbnbcqiUI3GW69rtWbt5IfH3AUEo2AchaZbRNO127q2XbfJmQdO07TwjYzgv1veh4NOXKz5ffefu/ifXN3d8/faDm9v7fwlndYaqHTkVHVpbZTOZjXWWVsnDo8/fnc8xTvADwZ+HHeIQvSNcbP4ODNe/NP24KF0G/n6GH2EjXJVcvNuWr4Tr2rqZ43KtN8JXymq2gqQ5ExwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Cahoots&quot;
        title=&quot;Cahoots&quot;
        src=&quot;/static/7168019d4007fcce1862971021646b46/fcda8/cahoots_screenshot.png&quot;
        srcset=&quot;/static/7168019d4007fcce1862971021646b46/12f09/cahoots_screenshot.png 148w,
/static/7168019d4007fcce1862971021646b46/e4a3f/cahoots_screenshot.png 295w,
/static/7168019d4007fcce1862971021646b46/fcda8/cahoots_screenshot.png 590w,
/static/7168019d4007fcce1862971021646b46/efc66/cahoots_screenshot.png 885w,
/static/7168019d4007fcce1862971021646b46/c83ae/cahoots_screenshot.png 1180w,
/static/7168019d4007fcce1862971021646b46/1e5d2/cahoots_screenshot.png 1630w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In March, I helped out at a church event where we found out Kahoot had unexpectedly restricted the free plan. I thought it’d be cool if there could be a “self-hosted” version for non-profits, and made a really non-user-friendly-but-free prototype with GitHub Pages / React / Airtable&lt;/li&gt;
&lt;li&gt;It had some real UX deficiencies to make it free for me to host, so I wanted to have some testers to see if the tradeoffs were ok before deciding to promote it&lt;/li&gt;
&lt;li&gt;I asked a friend to try it but didn’t really follow-up, and also made a post on Reddit (r/react), more a technical post (not much interaction there though)&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;it was fun to give the Airtable API a try, also a good opportunity to get a little more familiar with Promises&lt;/li&gt;
&lt;li&gt;I definitely didn’t feel comfortable promoting the initial version of the project, there were so many hoops someone had to jump through just to even try it (setting up a throwaway Airtable account for example). In hindsight, this was probably a mistake - these are all guesses &gt;_&amp;#x3C;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;apr-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#apr-2020&quot; aria-label=&quot;apr 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Apr 2020&lt;/h2&gt;
&lt;h3 id=&quot;a-newsletter-scraping-project&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-newsletter-scraping-project&quot; aria-label=&quot;a newsletter scraping project permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A newsletter scraping project&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 590px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/901f9f255c7e292a6616408fa84cabda/193c9/til.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102.7027027027027%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAVABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECBQb/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAe/iw2g8vTUA/8QAGxAAAQQDAAAAAAAAAAAAAAAAAAEDERQCECH/2gAIAQEAAQUCMVnUocJLTxaeLTx//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAGhAAAgIDAAAAAAAAAAAAAAAAADIBoREgIf/aAAgBAQAGPwInmNWoahqP/8QAHBAAAgICAwAAAAAAAAAAAAAAAAERIRDwMWGR/9oACAEBAAE/IRt1Dw5LRPCMOr4NKG1D/9oADAMBAAIAAwAAABDbLzz/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAVEQEBAAAAAAAAAAAAAAAAAAARIP/aAAgBAgEBPxAj/8QAHBAAAgEFAQAAAAAAAAAAAAAAAAERIUFRYfAx/9oACAEBAAE/EKIaSKBTfZCwMQqRdCrQ0tCILxEvrusDxONGnxo//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;todayilearn&quot;
        title=&quot;todayilearn&quot;
        src=&quot;/static/901f9f255c7e292a6616408fa84cabda/1c72d/til.jpg&quot;
        srcset=&quot;/static/901f9f255c7e292a6616408fa84cabda/a80bd/til.jpg 148w,
/static/901f9f255c7e292a6616408fa84cabda/1c91a/til.jpg 295w,
/static/901f9f255c7e292a6616408fa84cabda/1c72d/til.jpg 590w,
/static/901f9f255c7e292a6616408fa84cabda/a8a14/til.jpg 885w,
/static/901f9f255c7e292a6616408fa84cabda/193c9/til.jpg 1093w&quot;
        sizes=&quot;(max-width: 590px) 100vw, 590px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There were some really useful newsletters I had been following, but I wished there was a way to consume the articles shared in a more piecemeal manner. For example, Hackernewsletter includes &gt;50 articles each week, I usually end up opening 15 tabs in my mobile Firefox and then slowly consuming them over the week - which you can guess isn’t very successful.&lt;/li&gt;
&lt;li&gt;Spent a week or two repurposing an old project and started scraping the web archives of Hackernewsletter. (I later found out that Hackernewsletter had stopped publishing their archives, so that wouldn’t really work)&lt;/li&gt;
&lt;li&gt;Showed the idea to a friend, who gave some really useful feedback (being able to retrieve the knowledge when it would be useful was a bigger problem than consuming it)&lt;/li&gt;
&lt;li&gt;Also, I couldn’t think of a way to give due credit back to the newsletter creators, unless I worked directly with them (feels like a chicken and egg thing) and I didn’t want to disrupt their income source.&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;The turnaround time was pretty OK (2 weeks) so not a huge loss&lt;/li&gt;
&lt;li&gt;However, I probably should have led by talking to more people instead of building the backend first - since it was mostly repurposing I didn’t really learn anything new from the technical standpoint&lt;/li&gt;
&lt;li&gt;Also, I didn’t really pursue the knowledge surfacing problem that he raised, because it was pretty nebulous and I was… lazy. Looking at the emphasis on knowledge management solutions now, it does seem like that was another mistake 😞&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;jun-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#jun-2020&quot; aria-label=&quot;jun 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Jun 2020&lt;/h2&gt;
&lt;h3 id=&quot;a-self-hosted-real-time-kahoot-clone&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-self-hosted-real-time-kahoot-clone&quot; aria-label=&quot;a self hosted real time kahoot clone permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A &lt;del&gt;“self-hosted”&lt;/del&gt; real-time Kahoot! clone&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This actually still works, feel free to give it a &lt;a href=&quot;https://wasabigeek.github.io/cahoots/#/&quot;&gt;try!&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;I thought it would be an interesting technical exercise to try and make the previous project real-time. Wanted to try something more DIY like socket.io, ended up using Firebase.&lt;/li&gt;
&lt;li&gt;I didn’t really do much validation except showing it to 2 friends, with a lukewarm response&lt;/li&gt;
&lt;li&gt;I also decided against pursuing it further in it’s current form:
&lt;ul&gt;
&lt;li&gt;Competing with Kahoot! on price was probably not going to work, their plans were still pretty affordable (and it seems, even cheaper now)&lt;/li&gt;
&lt;li&gt;They were focusing on education, so I thought another angle was a casual party game, which incidentally was how I was introduced to Kahoot. Something like an &lt;a href=&quot;https://en.wikipedia.org/wiki/Bishi_Bashi&quot;&gt;MMO bishi-bashi&lt;/a&gt;. I shelved this for a future date because I felt like I didn’t have the expertise for building web games, so it would be a long-build.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reflections
&lt;ul&gt;
&lt;li&gt;Even if it wasn’t exactly what I initially wanted to learn (Firebase abstracted a lot of the real-time aspects), I did walk away with some more knowledge of NoSQL databases, plus some Firebase-specific knowledge (e.g. security rules), which was great.&lt;/li&gt;
&lt;li&gt;Again, didn’t really push on the validation front, possibly because I felt it needed more polish, plus I wasn’t sure if I was going to stick with Firebase if moving to the variety-game idea&lt;/li&gt;
&lt;li&gt;I do feel like the rise of games like Fall Guys and Among Us somewhat validates the MMO casual game… 🤔&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;jul-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#jul-2020&quot; aria-label=&quot;jul 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Jul 2020&lt;/h2&gt;
&lt;h3 id=&quot;over-the-kit&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#over-the-kit&quot; aria-label=&quot;over the kit permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Over the Kit&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This is still alive! &lt;a href=&quot;https://wasabigeek.github.io/drumz/&quot;&gt;https://wasabigeek.github.io/drumz/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Me being a very lazy drummer, just want to be given some licks to practice instead of having to orchestrate and build patterns up to speed myself&lt;/li&gt;
&lt;li&gt;Built a small, limited prototype (only 4/4, only 16th notes 1 bar fill) then tried to do some validation
&lt;ul&gt;
&lt;li&gt;Posted on Reddit r/drums, relatively speaking there was very little interest (few upvotes and comments compared to what seems to usually trend there)&lt;/li&gt;
&lt;li&gt;Pinged 3 musician friends - feedback was more in the “oh this is cool” sorta vein (I need to dig up those messages)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Made some upgrades (the biggest being multiple usages: part-bar, 1-bar, 1-bar 16th note triplets variations)&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;I used Vexflow and WebAudio, which was really interesting&lt;/li&gt;
&lt;li&gt;r/drums might not be the right subreddit, given the focus there seems to be showing off 😋  maybe I should try r/drumming (which has less people but seems more educational) or see if there are some groups / chats I can join to talk with people&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;aug-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#aug-2020&quot; aria-label=&quot;aug 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Aug 2020&lt;/h2&gt;
&lt;h3 id=&quot;habitstar&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#habitstar&quot; aria-label=&quot;habitstar permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;HabitStar&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;I never made it to releasing on the App Store, but some screens of the prototype still exist on &lt;a href=&quot;https://getmakerlog.com/tasks/305538&quot;&gt;Makerlog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The thought process was something like, I’ve always wanted to try to build a mobile app + mobile apps seem to make a nice indie hacker project (could push infra to Apple via saving on device/iCloud, so it sounded like it’d be easier for one person to maintain than a web app)&lt;/li&gt;
&lt;li&gt;Initially thought to convert Over the Kit to an app, but after working with and digging into React Native for a bit, it seemed like it would be a challenge (due to Javascript’s event loop), so I thought I’d start with something simpler&lt;/li&gt;
&lt;li&gt;I always wanted to make a habit tracker, because the one I was using still felt a little clunky&lt;/li&gt;
&lt;li&gt;Got a little bogged down trying to (i.) make a design I liked; (ii.) figure out the gamification mechanics (which I wanted to have in order to differentiate it). My lack of experience around mobile development and React Native (data storage, testing and debugging) made the experience slow and frustrating at times as well.&lt;/li&gt;
&lt;li&gt;At one point I was wondering whether to set the gamification part aside for a bit to just focus on getting the app out. But I thought the gamification was part of the charm, and didn’t want to release without it (the $99 developer fee also was a small discourager)&lt;/li&gt;
&lt;li&gt;In the end, I just drifted to something else…&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;The design part especially seemed to bog me down mentally (“I wish it looked better”). Maybe I shouldn’t let myself get too discouraged with a sub-standard design. Or maybe I should try to get more comfortable with design?&lt;/li&gt;
&lt;li&gt;I’m sure the “slow and frustrating” feeling contributed to me drifting away - but I should actually try to get used to being in that zone more, because it probably means I’m doing something way out of my comfort zone and learning something new.&lt;/li&gt;
&lt;li&gt;I finally got to try React Native after thinking about it for years, so that was great! I do wonder if the tooling around Flutter is better though, maybe will give that a bit of a try. If it leans towards the game aspects more too, I wonder if I should try something like Unity 🤔  All of the above sound like pretty significant time commitments.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;augoct-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#augoct-2020&quot; aria-label=&quot;augoct 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Aug/Oct 2020&lt;/h2&gt;
&lt;h3 id=&quot;classcal&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#classcal&quot; aria-label=&quot;classcal permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ClassCal&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Still a work in progress, there’s a &lt;a href=&quot;https://classcal-staging.herokuapp.com/&quot;&gt;staging app&lt;/a&gt; you can try, plus a quick and dirty wix landing page my friend made &lt;a href=&quot;https://www.classcal.com/&quot;&gt;https://www.classcal.com/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Discussed a few ideas with a friend before we decided to give a Tuition scheduling app a try, based on some earlier conversations he had while validating a different idea. For me, I was also curious about whether we could work together, since he had some experiences starting more traditional businesses, and seemed to be stronger in the sales aspect.&lt;/li&gt;
&lt;li&gt;In terms of validating the idea:
&lt;ul&gt;
&lt;li&gt;my friend did some demos with existing solutions in the market - we thought a possible angle was to try to do something more affordable and SaaS-like, since the incumbent solutions in the market were positioned more as custom solutions, with prices to match (though they could qualify for the govt. grants)&lt;/li&gt;
&lt;li&gt;we spoke with 2 of his contacts in the industry, the second one was positive but also seemed to have a non-traditional approach to the business, managing it more like a gym with online bookings and payments&lt;/li&gt;
&lt;li&gt;we tried to walk the ground and speak with some tuition centres, but this was super hard. Most people just shoo-ed us away (to be fair, it was true that they were busy)&lt;/li&gt;
&lt;li&gt;my friend also tried to post a poll in a Facebook groups, there was little response&lt;/li&gt;
&lt;li&gt;now we’re trying Google Ads with the landing page in a last-ditch attempt to get people to talk to and learn from, before we decide whether to move on to a different idea&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We agreed not to build anything beyond a Figma prototype until we had more validation, but I had itchy fingers by October and went to start on the app. It was a nice chance for me to figure out how to build a SaaS in Rails as well (I had tried some out-of-the-box template with the newsletter idea but it was not well-documented and I didn’t know enough / need all of the bells and whistles)&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;I think it’s worth spending time aligning with a partner on approaches and beliefs. Personally,  I realised I didn’t really know how to explain processes I had taken for granted, such as user story mapping.&lt;/li&gt;
&lt;li&gt;It was helpful having the second brain, we weren’t always on the same wavelength but it did push me to have a different perspective and read up stuff I’ve been putting off e.g. on validation&lt;/li&gt;
&lt;li&gt;I think I will still continue building for a bit, because it does provide a potential future framework for launching other projects. In the worst case, I will open source it and try to do some walkthrough to building a SaaS in Rails.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;sep-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sep-2020&quot; aria-label=&quot;sep 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sep 2020&lt;/h2&gt;
&lt;h3 id=&quot;peekpalette&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#peekpalette&quot; aria-label=&quot;peekpalette permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;PeekPalette&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The unfinished &lt;a href=&quot;https://wasabigeek.github.io/peekpalette/&quot;&gt;landing page&lt;/a&gt; is still up&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;While doing the Figma prototype, I spent quite a bit of time figuring out colour variations and thought, there must be an opportunity here - an app that would generate and let you preview colour combinations against a UI (something like Headlime, but for UI colour palettes)&lt;/li&gt;
&lt;li&gt;I decided to try starting from a landing page and validate that first. After spending a week on an interactive hero section, I realised I didn’t know what else to put in the “marketing” section of the site that would be convincing &gt;_&amp;#x3C;&lt;/li&gt;
&lt;li&gt;In terms of validation, I did a half-hearted post on Makerlog and another on IndieHackers, again, very little feedback or interest.&lt;/li&gt;
&lt;li&gt;Also thought that the target might be so niche it didn’t exist: If I’m a dev that wasn’t good with design, I’d probably purchase some theme instead.&lt;/li&gt;
&lt;li&gt;I do think there’s some opportunity in this market, but this probably wouldn’t cut it as a standalone product&lt;/li&gt;
&lt;li&gt;Reflections:
&lt;ul&gt;
&lt;li&gt;I did enjoy working on the hero section, got to try out a rainbow CSS gradient I saw on another blog&lt;/li&gt;
&lt;li&gt;I didn’t enjoy trying to figure out the design of the UI on the interactive element, and also figuring out the marketing copy. That’s also probably the point when I started to realise the idea didn’t have legs. In this case, might have been helpful to validate more first before building, but I think I did ok here, didn’t “waste” too much time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;dec-2020&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dec-2020&quot; aria-label=&quot;dec 2020 permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dec 2020&lt;/h2&gt;
&lt;h3 id=&quot;baseui-working-title&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#baseui-working-title&quot; aria-label=&quot;baseui working title permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;BaseUI (working title)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Continuing the theme of “I wish there was an easier way to do a nice design”, I’m currently trying to validate whether my problem of getting a decent but still unique design done quickly applies to other IH devs:
&lt;ul&gt;
&lt;li&gt;Inspired by the Mom Test, I tried not to rush into building, but tried to understand the target’s (IH devs) current workflow a bit more with a post on IH &lt;a href=&quot;https://www.indiehackers.com/post/serial-saas-devs-whats-your-workflow-when-starting-a-new-project-7d4c43a2a3&quot;&gt;https://www.indiehackers.com/post/serial-saas-devs-whats-your-workflow-when-starting-a-new-project-7d4c43a2a3&lt;/a&gt; (it was also useful for me)&lt;/li&gt;
&lt;li&gt;I looked at other solutions in the market: full-app templates like JumpstartPro and pure UI component libraries like TailwindUI, thought there might be an opportunity to do something in the middle.&lt;/li&gt;
&lt;li&gt;I’m now trying to build (oops) a &lt;a href=&quot;https://github.com/wasabigeek/rails-templates/tree/main/base_ui&quot;&gt;free, minimal set of components&lt;/a&gt; that’s integrated with Rails, and seeing whether there’s interest in that before going further. Again, struggling a little with design here (I would love to just plonk TailwindUI in there, but it goes against their license, and I’m not sure if there’s a cheap way to get a neat set of components)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Customising Generator Templates in Rails (Gems too!)]]></title><description><![CDATA[Rails’ generators are a great boon to the Indie HackerTM or Solo DeveloperTM. Running  will quickly bootstrap the full stack for a model…]]></description><link>https://www.wasabigeek.com/blog/customising-generator-templates-in-rails-gems-too/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/customising-generator-templates-in-rails-gems-too/</guid><pubDate>Sat, 07 Nov 2020 20:32:03 GMT</pubDate><content:encoded>&lt;p&gt;Rails’ generators are a great boon to the Indie Hacker&lt;sup&gt;TM&lt;/sup&gt; or Solo Developer&lt;sup&gt;TM&lt;/sup&gt;. Running &lt;code class=&quot;language-text&quot;&gt;rails g scaffold ...&lt;/code&gt; will quickly bootstrap the full stack for a model, creating the routes, controllers, views and even tests.&lt;/p&gt;
&lt;p&gt;As you start customising and standardising your interface though, the default templates get less and less useful - most of the generated code doesn’t fit your layout or setup, and you end up deleting most of it or skipping them.&lt;/p&gt;
&lt;h2 id=&quot;what-if-we-could-change-the-templates&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-if-we-could-change-the-templates&quot; aria-label=&quot;what if we could change the templates permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What if we could change the… templates?&lt;/h2&gt;
&lt;p&gt;Rails actually gives you a lot of &lt;a href=&quot;https://guides.rubyonrails.org/generators.html&quot;&gt;control&lt;/a&gt; over the generators, but early on in a project, perhaps the most impactful &lt;em&gt;and&lt;/em&gt; simple thing to do is to override the &lt;a href=&quot;https://guides.rubyonrails.org/generators.html#customizing-your-workflow-by-changing-generators-templates&quot;&gt;templates&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s look at an example. Normally Rails would generate a &lt;code class=&quot;language-text&quot;&gt;show.html&lt;/code&gt; that looks something like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;erb&quot;&gt;&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;notice&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; notice &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Attribute:&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;strong&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attribute &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; link_to &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Edit&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; edit_object_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt; |
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; link_to &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Back&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object_path &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For a personal project, I’ve made mine generate something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;erb&quot;&gt;&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; content_for &lt;span class=&quot;token symbol&quot;&gt;:breadcrumbs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    Home &gt; &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attribute &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; content_for &lt;span class=&quot;token symbol&quot;&gt;:heading&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; link_to &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Edit object&apos;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; edit_object_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&apos;...&apos;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; content_for &lt;span class=&quot;token symbol&quot;&gt;:main_content&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%=&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token variable&quot;&gt;@object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;attribute &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s saved me a lot of time!&lt;/p&gt;
&lt;p&gt;To do so, simply add a template to your Rails project in &lt;code class=&quot;language-text&quot;&gt;lib/templates/erb/scaffold/show.html.erb&lt;/code&gt;. You can use the &lt;a href=&quot;https://github.com/rails/rails/blob/master/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt&quot;&gt;original&lt;/a&gt; as a starting point. The other templates can also be easily changed following a similar path. Note the double percentages (&lt;code class=&quot;language-text&quot;&gt;%%&lt;/code&gt;) used to escape ERB tags!&lt;/p&gt;
&lt;h2 id=&quot;bonus-gems-too&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bonus-gems-too&quot; aria-label=&quot;bonus gems too permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bonus: Gems too!&lt;/h2&gt;
&lt;p&gt;This also works for your gems that have generators (e.g. &lt;a href=&quot;https://github.com/rspec/rspec-rails&quot;&gt;rspec-rails&lt;/a&gt; or &lt;a href=&quot;https://github.com/reactjs/react-rails&quot;&gt;react-rails&lt;/a&gt;), though it’s not obvious where to place the templates.&lt;/p&gt;
&lt;p&gt;Our answer lies in &lt;a href=&quot;https://github.com/rails/rails/blob/master/railties/lib/rails/generators/base.rb&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Rails::Generators::Base.inherited&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ruby&quot;&gt;&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inherited&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;base&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#:nodoc:&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
  Rails&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;Generators&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;templates_path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;::&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source_paths &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;base_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generator_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source_paths &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generator_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, in addition to the &lt;code class=&quot;language-text&quot;&gt;source_root&lt;/code&gt; that most Generators would define, Rails will also search a few extra paths. In general, you should place the template in &lt;code class=&quot;language-text&quot;&gt;lib/templates/&amp;lt;top_level_module&gt;/&amp;lt;generator_class_prefix&gt;/&amp;lt;template_name&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, &lt;code class=&quot;language-text&quot;&gt;react-rails&lt;/code&gt; has &lt;a href=&quot;https://github.com/reactjs/react-rails/blob/master/lib/generators/react/component_generator.rb&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;React::Generators::ComponentGenerator&lt;/code&gt;&lt;/a&gt;, so if I wanted to replace &lt;a href=&quot;https://github.com/reactjs/react-rails/blob/master/lib/generators/templates/component.es6.jsx&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;component.es6.jsx&lt;/code&gt;&lt;/a&gt;, I would place it in &lt;code class=&quot;language-text&quot;&gt;lib/templates/react/component/component.es6.jsx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I hope that helps!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[First Steps into Open Source]]></title><description><![CDATA[At start of the year, I set a rather nebulous goal: “become a regular open source contributor” The motivation was to experience different…]]></description><link>https://www.wasabigeek.com/blog/first-steps-to-open-source/</link><guid isPermaLink="false">https://www.wasabigeek.com/blog/first-steps-to-open-source/</guid><pubDate>Wed, 06 May 2020 22:12:03 GMT</pubDate><content:encoded>&lt;p&gt;At start of the year, I set a rather nebulous goal:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“become a regular open source contributor”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The motivation was to experience different codebases to gain more experience and (hopefully) improve my employability. 5 months on, I feel reasonably comfortable stating that I’m an open source contributor, and hope to continue doing so.&lt;/p&gt;
&lt;p&gt;This is a summary of my journey so far, I hope it’ll be useful for you, especially if you’re thinking of contributing!&lt;/p&gt;
&lt;h2 id=&quot;first-timers-only&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#first-timers-only&quot; aria-label=&quot;first timers only permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;First Timers Only&lt;/h2&gt;
&lt;p&gt;To start, I remembered reading about a &lt;a href=&quot;https://kentcdodds.com/blog/first-timers-only&quot;&gt;first timers “movement”&lt;/a&gt; and quickly found a compilation of participating &lt;a href=&quot;https://github.com/MunGell/awesome-for-beginners&quot;&gt;projects&lt;/a&gt;. Ruby was what we used at work, so I jumped to that section.&lt;/p&gt;
&lt;p&gt;The first &lt;a href=&quot;https://github.com/rails/rails/pull/38220&quot;&gt;PR&lt;/a&gt; I tried to submit was for Rails (seemed like it would be something nice on the resume). Rails uses the label &lt;a href=&quot;https://github.com/rails/rails/labels/good%20first%20issue&quot;&gt;good first issue&lt;/a&gt;, so I picked an issue near the top, spent a Saturday working and submitted it!&lt;/p&gt;
&lt;p&gt;Then I waited.&lt;/p&gt;
&lt;p&gt;And waited.&lt;/p&gt;
&lt;h2 id=&quot;good-things&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#good-things&quot; aria-label=&quot;good things permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Good things…&lt;/h2&gt;
&lt;p&gt;In hindsight, that issue was not a great first pick. It already had some attempts, with the most recent just a week before my PR. I think I was so unsure of myself, I figured this would be a super low-risk issue to try. After all, if I screwed it up, there were other attempts that could be picked up.&lt;/p&gt;
&lt;p&gt;In the end, one of the maintainers took it upon himself to close the issue and credited all the contributors. His PR was better, but I guess it was mainly a way of navigating this long-drawn issue without stepping on any toes.&lt;/p&gt;
&lt;p&gt;While waiting, I decided to try again. We were using a &lt;a href=&quot;https://github.com/99designs/iamy&quot;&gt;tool&lt;/a&gt; written in Go to manage our AWS IAM at work, but there was a good-to-have feature we wanted and an issue for it. So I spent a few week nights and weekends learning Go, doing up the &lt;a href=&quot;https://github.com/99designs/iamy/pull/69&quot;&gt;PR&lt;/a&gt; and submitting it.&lt;/p&gt;
&lt;p&gt;Then I waited.&lt;/p&gt;
&lt;h2 id=&quot;come-to-those&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#come-to-those&quot; aria-label=&quot;come to those permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;…come to those…&lt;/h2&gt;
&lt;p&gt;And someone reviewed! I was energised. I made some changes.&lt;/p&gt;
&lt;p&gt;Then he requested a second opinion from another maintainer.&lt;/p&gt;
&lt;p&gt;It’s still there, open.&lt;/p&gt;
&lt;p&gt;At this point I was starting to get disheartened. (No hate to open source maintainers, it’s pretty thankless work and the world is better for your contributions.)&lt;/p&gt;
&lt;h2 id=&quot;who-wait-keep-trying&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#who-wait-keep-trying&quot; aria-label=&quot;who wait keep trying permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;…who &lt;del&gt;wait&lt;/del&gt; keep trying.&lt;/h2&gt;
&lt;p&gt;I flapped around a bit, and one day decided to look at another gem our company had been using. I saw a pretty steady stream of merges, neatly labelled issues, plus it just felt less intimidating than Rails.&lt;/p&gt;
&lt;p&gt;A few days later, my &lt;a href=&quot;https://github.com/slack-ruby/slack-ruby-bot/pull/244&quot;&gt;first merge&lt;/a&gt;! I was elated.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 518px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a6a8e9110ff40b0c37628a8d0267e82e/6b9fd/slackrubymerge.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 14.18918918918919%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAh0lEQVQI16XOTQ6CMBiEYe6/9hqehg0JxmAQCm0JPwbs15+8huoNnOTJbGYxxdjuBBHkJ8aI9z6TkwjOOZwTQoj4kPC5v859OPfiSSlRXC836rKkrirq252uV6hhxBiLNhalBprmQdf1TPOLp92zVm/Zsu1MdkJrk08UXiLH8WZe1mxZN/7JBzMq5yu8m2zxAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;First Merge!&quot;
        title=&quot;First Merge!&quot;
        src=&quot;/static/a6a8e9110ff40b0c37628a8d0267e82e/6b9fd/slackrubymerge.png&quot;
        srcset=&quot;/static/a6a8e9110ff40b0c37628a8d0267e82e/12f09/slackrubymerge.png 148w,
/static/a6a8e9110ff40b0c37628a8d0267e82e/e4a3f/slackrubymerge.png 295w,
/static/a6a8e9110ff40b0c37628a8d0267e82e/6b9fd/slackrubymerge.png 518w&quot;
        sizes=&quot;(max-width: 518px) 100vw, 518px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Huge props to &lt;a href=&quot;https://github.com/dblock&quot;&gt;dblock&lt;/a&gt;, who somehow manages to turnaround and reply quickly, often within a day or two. Rubyists, if you would love to contribute but don’t know where to, might I humbly suggest anyone of dblock’s projects - it’s a great place to start.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#closing-thoughts&quot; aria-label=&quot;closing thoughts permalink&quot; class=&quot;autolink-anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;For “first timers” looking to contribute, my takeaways (sample size of 1, take with a pinch of salt):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Labels on recent issues seemed like a good sign that a project is well-maintained. Search for a &lt;code class=&quot;language-text&quot;&gt;first-timers-only&lt;/code&gt; type label - for example, in Rails it was &lt;code class=&quot;language-text&quot;&gt;good first issue&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the &lt;code class=&quot;language-text&quot;&gt;CONTRIBUTING.md&lt;/code&gt; documentation first before starting on your PR! There’s usually a lot of helpful setup stuff there. For example, Rails was pretty amazing for this, I was kinda worried going in but it was really smooth, they even had instructions on how to use Vagrant to get started.&lt;/li&gt;
&lt;li&gt;Expect a little trial-and-error to getting your first PR merged, but stick with it! I found the experience super rewarding, and indeed got exposure to different codebases and languages.&lt;/li&gt;
&lt;li&gt;Make life as easy for maintainers as possible - try to figure out as much as you can first, whether it’s looking at previous PRs, making GitHub checks pass.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope that was helpful! Feel free to drop a note on Twitter to discuss / disagree :)&lt;/p&gt;</content:encoded></item></channel></rss>