<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aha!</title>
    <description>The latest articles on DEV Community by Aha! (@aha).</description>
    <link>https://dev.to/aha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2913%2F85ee260d-6099-4d25-ac8c-890e4168b425.png</url>
      <title>DEV Community: Aha!</title>
      <link>https://dev.to/aha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aha"/>
    <language>en</language>
    <item>
      <title>Building a dynamic Canvas rendering engine using JSX</title>
      <dc:creator>Percy Hanna</dc:creator>
      <pubDate>Tue, 02 Apr 2024 15:59:38 +0000</pubDate>
      <link>https://dev.to/aha/building-a-dynamic-canvas-rendering-engine-using-jsx-3237</link>
      <guid>https://dev.to/aha/building-a-dynamic-canvas-rendering-engine-using-jsx-3237</guid>
      <description>&lt;p&gt;Our product team is busy adding many great new features to &lt;a href="https://www.aha.io/whiteboards/overview"&gt;Aha! Whiteboards&lt;/a&gt; and &lt;a href="https://www.aha.io/knowledge/overview"&gt;Aha! Knowledge&lt;/a&gt; — including wireframes, voting, and improvements to viewing &lt;a href="https://www.aha.io/roadmaps/overview"&gt;Aha! Roadmaps&lt;/a&gt; data within a whiteboard. We added all of this functionality in just the last few months, and we are busy building even more features that will deliver product value to our users.&lt;/p&gt;

&lt;p&gt;As the engineering lead for &lt;a href="https://www.aha.io/blog/aha-expands-product-development-suite-with-new-whiteboarding-and-knowledge-base-tools"&gt;these products&lt;/a&gt;, I saw all of the above features (and others) on our product roadmap and had a few thoughts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How can we easily build features that rely on dynamic content and real-time user collaboration?&lt;/li&gt;
&lt;li&gt;How can we do that on Canvas? Rendering dynamic content on Canvas is complicated — or at least, it's much more difficult than using HTML and React, which is how we have built most interactive features at Aha!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;Anyone who has used Canvas in the past understands the challenge. You cannot simply render content to the screen — you must manually draw content using functions such as &lt;code&gt;lineTo&lt;/code&gt;, &lt;code&gt;fillRect&lt;/code&gt;, &lt;code&gt;fillText&lt;/code&gt;, and so on. Consider the really simple example of rendering "Hello, world!" with basic styling and a quick layout. Let's try and reproduce this HTML in Canvas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border: solid 1px black; padding: 5px; font: 16px Times;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Hello, world!
&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will produce this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1cyv1qbykc2waew1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1cyv1qbykc2waew1g.png" alt="Hello world!" width="113" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But let's see what we would need to generate this in Canvas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rendering context&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Measure the text&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;16px Times&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textBaseline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate the box size&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxDescent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Draw the border&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strokeRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Render the text&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost 20 lines of code just to render the most basic of content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic content
&lt;/h2&gt;

&lt;p&gt;This becomes even more complex when you start adding dynamic content. For example, one of the first features in our Aha! Whiteboards roadmap was to add emoji reactions to our sticky note shape. This is a simple way to gather feedback from everyone viewing the whiteboard. The feature seems simple enough. In React/HTML, you might have something that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 1
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is what the user would see:&lt;/p&gt;

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

&lt;p&gt;In Canvas, this becomes much more difficult. We have to render the button, add the padding and border radius, and then render the emoji and reaction number.&lt;/p&gt;

&lt;p&gt;But it doesn't stop there: What if 10 people react with a thumbs-up? Or 100? Now, that button must be wider to adapt to the content inside of it. In HTML, you don't even have to think about this. It just happens. You would just write your code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chances are that's how your component was written in the first place. You didn't even think about how to resize the &lt;code&gt;button&lt;/code&gt; element — the browser's rendering engine just did that for you. Emoji reactions was just the first feature I saw in our roadmap. And there were many others. But the big, daunting one was wireframes.&lt;/p&gt;

&lt;p&gt;Not only did we need a way to render a button that was entirely in our control, but we also needed to allow users to create their own buttons and add their own content to it. And a button was one of the simpler components we needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive solution
&lt;/h2&gt;

&lt;p&gt;The very naive solution would be to write each button and wireframe shape separately. You could perhaps create a &lt;code&gt;renderButton&lt;/code&gt; function as a helper to render a button to the Canvas. Generalize the above code into a function, and you come up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Rendering context&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate the box size&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxDescent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Draw the button background&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;roundRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Render the text&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this solution is limited. It only solves a specific problem and doesn't help fill in the functionality gap that HTML addresses out of the box. Imagine how this would scale as you add more complex shapes like these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation menu:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Dropdown picker:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;So the complexity of rendering all of these wireframes grows quickly, even with relatively simple shapes. How do you calculate the width and height of each navigation item? How do you render the borders, backgrounds, and dividers between each item?&lt;/p&gt;

&lt;h2&gt;
  
  
  Buttons
&lt;/h2&gt;

&lt;p&gt;Let's take a deeper look at a very simple shape we have in our Wireframes: the button. As I demonstrated above, implementing a basic button renderer would not be difficult in and of itself. But there is already a bit of added complexity here. Our button needed to support multiple styles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlorcym15tv9qen35lq1.png" alt="Basic button" width="304" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhge31xz1o1e7z73lg23.png" alt="Rounded button" width="344" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outline&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbwepeuf19dpezr6dn3d.png" alt="Outline button" width="304" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded outline&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cxtztg09xm0pllt1b10.png" alt="Rounded outline button" width="344" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rlg8h046vcu7fz6ovlb.png" alt="Link button" width="268" height="84"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these variations adds a bit more code and complexity to our &lt;code&gt;renderButton&lt;/code&gt; function. It would definitely be possible to support all of these designs with a single function, but would it be elegant? Reusable? Dynamic?&lt;/p&gt;

&lt;p&gt;Did you notice the icons? Well, users can choose to place the icon on the right side instead. And they can change the color of the button and the text, too. By themselves, each of these are minor things to add. But eventually, that function would become overloaded with all the variations, customizations, and edge cases that need to be handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSX
&lt;/h2&gt;

&lt;p&gt;In order for our team to be able to easily implement all of these features, we needed a readable, elegant, and powerful way to render dynamic content to Canvas in a familiar way. I thought to myself, wouldn't it be great if we could just render content to Canvas the same way we do with React?&lt;/p&gt;

&lt;p&gt;And then I had my &lt;a href="https://www.aha.io/company/history"&gt;Aha! moment&lt;/a&gt;. WE COULD! To do this, we would use one of the really powerful and innovative features that React introduced to the world — something called JSX. It's really just syntactic sugar to write HTML syntax into declarative and well-structured element objects. For those of you who are unfamiliar with JSX, what it does for you under the hood is convert code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Into something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emoji-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👍 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSX, in my opinion, was the &lt;a href="https://react.dev/learn/writing-markup-with-jsx"&gt;biggest innovation&lt;/a&gt; that came out of React given that you could write HTML directly in your JavaScript. I had used other frameworks such as &lt;a href="https://mootools.net/"&gt;MooTools&lt;/a&gt; in the past. You could use them to dynamically construct HTML in JavaScript, but with a very verbose and tedious syntax. It looked very similar to the converted code above. And it worked, but it was not satisfying or pleasant.&lt;/p&gt;

&lt;p&gt;The beauty of JSX is that you can override how elements are converted to JavaScript code using something called a &lt;a href="https://en.wikipedia.org/wiki/Directive_(programming)"&gt;pragma&lt;/a&gt;. A pragma, or directive, is something that tells the JSX compiler how to process the code. In your JSX/TSX code, you can add a pragma to the top of your file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @jsx CanvaSX */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanvaSX&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvasx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of converting your JSX to React code, the above code would be converted to something called CanvaSX code (more on this later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nc"&gt;CanvaSX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emoji-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👍 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;My goal was simple and clear: I wanted to be able to write JSX code that could render a simple navigation/tab menu. The system should generate the layout and render that content for me dynamically, and I shouldn't have to think about the complexities of measuring text and padding and layouts. It would need to handle very common layout patterns such as margin, padding, (rounded) borders, and so on.&lt;/p&gt;

&lt;p&gt;The really powerful feature would be to have all the layout complexity completely hidden from the developer. When rendering the navigation items, the width of each item would need to be based on the text and/or icon inside it, and then the borders and background should be rendered based on the width of that content. We would then need to render dividers between each item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing CanvaSX
&lt;/h2&gt;

&lt;p&gt;I spent some time prototyping a rough implementation of a rendering engine that I've come to call CanvaSX (Canvas+JSX). The early versions were quite rough, but the benefits were obvious. I could write simple shapes like a button or navigation menu using very familiar syntax, and I didn't even have to think about calculating coordinates or layouts.&lt;/p&gt;

&lt;p&gt;Eventually, I added support for basic interaction properties (such as onClick and tooltip handlers) and some very rudimentary flexbox-like components, too. Now, we could have something like a real button completely rendered in Canvas. The end result made the emoji button as simple as this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emojiClickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emojiTooltipContent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FlexRow&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;verticalAlignment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'center'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Emoji&lt;/span&gt; &lt;span class="na"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;votes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FlexRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which made sure we could offer reactions like these:&lt;/p&gt;

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

&lt;p&gt;The wireframe button is very similar, but it simply passes along user input to the properties. For example, to support rounded vs. square buttons, we just need to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is very easy to read for anyone familiar with React, and CanvaSX addresses all of the layout complexities.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;The magic behind CanvaSX is that every component can measure itself. With this information, CanvaSX can generate the layouts automatically. This is what the &lt;code&gt;Text&lt;/code&gt; component's measuring function might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calculateDimensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A parting note
&lt;/h2&gt;

&lt;p&gt;Building CanvaSX is one of the highlights of my career. I have had the opportunity to work on many features in my more than five years of working at Aha! These include capacity planning for teams, redesigned drawers, &lt;a href="https://www.aha.io/develop/overview"&gt;Aha! Develop&lt;/a&gt;, and our custom card layout editor. I've recently worked on many improvements to Aha! Whiteboards, which is where CanvaSX came in. It has proven itself to be extremely powerful and flexible — and it is now used as the foundation for &lt;a href="https://www.aha.io/blog/introducing-emoji-reactions-on-whiteboards"&gt;emoji reactions&lt;/a&gt;, &lt;a href="https://www.aha.io/whiteboards/wireframes"&gt;wireframes&lt;/a&gt;, updated &lt;a href="https://www.aha.io/whiteboards/roadmaps-integration"&gt;record cards&lt;/a&gt;, and our brand-new &lt;a href="https://www.aha.io/whiteboards/product-prioritization"&gt;voting tool&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I work with a team of extremely talented engineers at Aha! We collaborate to solve hard problems and deliver value to our users. If this sounds like something you would enjoy doing, you should check out our &lt;a href="https://www.aha.io/company/careers/current-openings"&gt;open positions&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>canvas</category>
      <category>jsx</category>
      <category>whiteboards</category>
    </item>
    <item>
      <title>Ruby enumerables considered helpful</title>
      <dc:creator>Justin Weiss</dc:creator>
      <pubDate>Thu, 12 Oct 2023 19:36:12 +0000</pubDate>
      <link>https://dev.to/aha/ruby-enumerables-considered-helpful-15i</link>
      <guid>https://dev.to/aha/ruby-enumerables-considered-helpful-15i</guid>
      <description>&lt;p&gt;Ruby's &lt;a href="https://ruby-doc.org/3.2.2/Enumerable.html"&gt;Enumerable methods&lt;/a&gt; help you make powerful code simple — by filtering, transforming, and processing data like the best engineers do. These methods are available on Arrays, Hashes, and many (many) other objects, and similarly-named methods are available on even more. If you don't already know these methods well, then the most valuable time you can spend in Ruby is on mastering them.&lt;/p&gt;

&lt;p&gt;But not all data can take advantage of Enumerable methods, at least not directly.  What if you don't have an Array or Hash? Can you still use the Enumerable methods?  With all the time you spent becoming an Enumerable master, wouldn't it be nice if you could treat more things like Enumerables — like lists of items across multiple pages, or even items that slowly stream in from an API over time?&lt;/p&gt;

&lt;p&gt;Ruby's Enumerator class is exactly what you need to do this. It's not the easiest thing to understand, but a few examples can show you how to use it in your own applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic pagination
&lt;/h2&gt;

&lt;p&gt;Sometimes your data &lt;em&gt;seems&lt;/em&gt; like a natural fit for an Enumerable, but it doesn't &lt;em&gt;quite&lt;/em&gt; work. What if you're using an API that paginates? The best you can get is an Enumerable per page, which makes it annoying to work with multiple pages at once. You could fetch all the pages and then flatten the list of lists… But now you're always fetching all the data (even if you don't need it) and you have to wait for every page to load before you can do anything.&lt;/p&gt;

&lt;p&gt;Instead, wrap your API calls in an Enumerator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paginated_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial_url&lt;/span&gt;

  &lt;span class="no"&gt;Enumerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns an Enumerator, an object that can be used like an Enumerable, responding to all of the core Enumerable methods you know and love: &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;first&lt;/code&gt;, and all the rest.&lt;/p&gt;

&lt;p&gt;When you call a method like &lt;code&gt;map&lt;/code&gt; on that Enumerator, that method will ask for the next object as it needs it. When asked, the body of the Enumerator yields the next object using the yielder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paginated_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
  &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial_url&lt;/span&gt;

  &lt;span class="no"&gt;Enumerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;

      &lt;span class="c1"&gt;# Yield each result to the caller&lt;/span&gt;
      &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;yielder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;last_page?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# get ready to fetch the next page&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_page_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Enumerator makes the request for the first page. Then, any time a record is needed from the first page, the Enumerator yields it to the caller. When you run out of records on the first page, the Enumerator fetches the next page and so on until you run out. If you don't need the next page, it won't fetch it.&lt;/p&gt;

&lt;p&gt;How does this look for the caller?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;paginated_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/things"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"selected"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy — you don't even have to care that the data spans multiple pages across multiple API requests. From the caller's point of view, it's just an ordinary list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Save a block for later
&lt;/h2&gt;

&lt;p&gt;While working on the &lt;a href="https://www.aha.io/support/suite/suite/collaboration/use-ai"&gt;Aha! AI writing assistant&lt;/a&gt;, we had two different ways of working with AI responses. For development and testing, the response should be returned all at once because it is easier to work with. For a user, though, it feels better for the response to arrive incrementally — streaming in so you can know early on if you're getting the result you wanted.&lt;/p&gt;

&lt;p&gt;Everything else was exactly the same, except for how the response was dealt with.  In one case, a string was returned. In another, bits of a string were yielded to the caller as they came in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;stream: &lt;/span&gt;&lt;span class="n"&gt;stream?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Burst response&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Stream response&lt;/span&gt;

&lt;span class="c1"&gt;# all kinds of streaming setup work&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# stream chunk to browser&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;# all kinds of streaming teardown work, stream error handling, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fine. But the API method calls look exactly the same, except one takes a block and the other doesn't. And for streaming, you have extra work to do before and after the method call.&lt;/p&gt;

&lt;p&gt;It's hard to untangle this. You could pass around lambdas, &lt;code&gt;get&lt;/code&gt; could ignore a lambda parameter in stream mode, you could wrap the setup and teardown in its own block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;stream: &lt;/span&gt;&lt;span class="n"&gt;stream?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;streaming_setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# stream chunk to browser&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;stream?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maybe this will work. Again, it's &lt;em&gt;fine&lt;/em&gt;. But this is Ruby, so we can do better than fine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ruby-doc.org/3.2.2/Object.html#method-i-to_enum"&gt;Object#to_enum&lt;/a&gt; is a method available on any object. You give it a method name and arguments and it returns an Enumerator. Whenever that Enumerator is enumerated, it will call the method you gave it, on the object you called it on, with the arguments you gave it, and it will pass along anything your method yields as the next value of the Enumerator.&lt;/p&gt;

&lt;p&gt;At first, it's hard to see how this is useful. In practice, this means you can capture the arguments a method was called with, but defer needing the block the method yields to until the part that processes your data needs it.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;to_enum&lt;/code&gt;, the caller can now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;stream: &lt;/span&gt;&lt;span class="n"&gt;stream?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stream?&lt;/span&gt;
    &lt;span class="n"&gt;stream_response&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# all kinds of setup work&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# stream each chunk to browser as it arrives&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;# all kinds of teardown work, stream error handling, etc.&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;client.get&lt;/code&gt; now returns either a string or an Enumerator. If it's an Enumerator, it will yield each part of the response as it's received. Because this code no longer needs the block at the same time as the arguments, all of the logic around streaming a response can go somewhere else. This is much less tangled up than the last version. How is this possible? By using &lt;code&gt;to_enum&lt;/code&gt; in the client when it's not given a block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="vi"&gt;@stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@stream&lt;/span&gt;
      &lt;span class="n"&gt;get_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;get_burst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;to_enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:get_stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nb"&gt;block_given?&lt;/span&gt;

    &lt;span class="n"&gt;some_api_request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_burst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# nothing interesting here, just a basic request and response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the client is in streaming mode and &lt;em&gt;isn't&lt;/em&gt; given a block, &lt;code&gt;get_stream&lt;/code&gt; returns an Enumerator that calls get_stream &lt;em&gt;with&lt;/em&gt; a block. The yielded chunk is passed back through to the Enumerator when the caller, or any other code that the caller hands the Enumerator to, uses it.&lt;/p&gt;

&lt;p&gt;This pattern — decoupling the place where you need the arguments from the place where you have the block — becomes even more powerful the further apart those places are separated. It's a nice pattern to understand, and it's possible because of Enumerators.&lt;/p&gt;

&lt;p&gt;Enumerators can be tricky at first. It's hard to see the use when you read about them in the docs. But they have an amazing ability to wrap Ruby code into objects that take advantage of the full power of Enumerable methods. It's valuable to use the knowledge you already have in more places, and Enumerators will help you do exactly that.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>CLI tools at Aha!</title>
      <dc:creator>Steve Lamotte</dc:creator>
      <pubDate>Mon, 14 Aug 2023 16:55:19 +0000</pubDate>
      <link>https://dev.to/aha/cli-tools-at-aha-4j5h</link>
      <guid>https://dev.to/aha/cli-tools-at-aha-4j5h</guid>
      <description>&lt;h2&gt;
  
  
  Take your command-line utilities to the next level with these Ruby gems
&lt;/h2&gt;

&lt;p&gt;Ruby has always been a great general-purpose scripting language and is often used to create command-line utilities. Many of these use the excellent&lt;br&gt;
&lt;a href="https://github.com/rails/thor" rel="noopener noreferrer"&gt;Thor&lt;/a&gt; gem to parse command-line options, but there's no escaping one fact: command-line utilities just aren't interesting. Never have been, never will be.&lt;/p&gt;

&lt;p&gt;However, since Ruby was released in 1995, developers have created some clever and useful libraries for making command-line utilities simpler to use, look at, and code.&lt;/p&gt;
&lt;h2&gt;
  
  
  I love creating command-line utilities
&lt;/h2&gt;

&lt;p&gt;I am part of the &lt;a href="https://www.aha.io/" rel="noopener noreferrer"&gt;Aha!&lt;/a&gt; platform team. Our primary focus is ensuring system reliability, but we are also responsible for providing developer tooling for the rest of the engineering team. One of our main tools is the &lt;code&gt;ops&lt;/code&gt;&lt;br&gt;
command-line utility that performs many mundane yet important tasks to simplify processes for our engineers. Here are a few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ops ecs deploy&lt;/code&gt; deploys an application to an AWS ECS environment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ops ecs ssh&lt;/code&gt; launches a shell on an existing application container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ops ecs shell&lt;/code&gt; spins up a new application container and launches a shell there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we make updates to our ops and similar CLI utilities, we often improve the user experience by taking advantage of various Ruby gems. With little effort compared to low-level coding with &lt;a href="https://github.com/ruby/curses" rel="noopener noreferrer"&gt;curses&lt;/a&gt;, our command-line utilities that used to be cryptic and confusing are now interactive, easy to use, and — dare I say — elegant.&lt;/p&gt;

&lt;p&gt;Let's talk about a few of these "gems." 😆&lt;/p&gt;
&lt;h2&gt;
  
  
  1. &lt;code&gt;cli-ui&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I really enjoy using the &lt;a href="https://github.com/Shopify/cli-ui" rel="noopener noreferrer"&gt;cli-ui&lt;/a&gt; gem. It offers a lot of powerful, intuitive features including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spinners: provide feedback that something is happening (e.g., track the progress of multi-threaded operations)&lt;/li&gt;
&lt;li&gt;frames: clearly demarcate logical groups of output including the ability to nest frames within each other&lt;/li&gt;
&lt;li&gt;prompts: provide a powerful UI for displaying and selecting a list of choices&lt;/li&gt;
&lt;li&gt;text formatting: provides several helpers to make text formatting quick and easy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we deploy code at Aha! we kick off a number of &lt;a href="https://aws.amazon.com/codedeploy/" rel="noopener noreferrer"&gt;AWS CodeDeploy&lt;/a&gt; tasks running in parallel. Here's some code to simulate deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'cli/ui'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;StdoutRouter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;
  &lt;span class="no"&gt;CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Deploying Aha! build &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;color: :blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SpinGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;spin_group&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;spin_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pod1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;spin_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pod2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;spin_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pod3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;CLI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt; &lt;span class="s2"&gt;"{{red:Deploy failed: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}}"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;deploy&lt;/span&gt; &lt;span class="s2"&gt;"123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces the following output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2Fbaf52e98af2c4903879f13f75de87fd8%2Fdeploy-spinners.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2Fbaf52e98af2c4903879f13f75de87fd8%2Fdeploy-spinners.gif" alt="deploy-spinners"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, our deploys aren't &lt;em&gt;quite&lt;/em&gt; this fast or simple. But you can imagine how useful it would be to see output with feedback like this when you have dozens of long-running threads executing in parallel, like our real deploys.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;terminal-table&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I used to write a lot of custom code to format tabular data in my CLI utilities. Thankfully, that stopped once I discovered &lt;a href="https://github.com/tj/terminal-table" rel="noopener noreferrer"&gt;terminal-table&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This gem is very easy to use and can process arrays as well as CSV data. Here's a quick example showing how simple it is to create nicely-formatted tabular data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'terminal-table'&lt;/span&gt;

&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Terminal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Shipment Summary"&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;border: :unicode_round&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Quantity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Unit Price"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Apples"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$1.99"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Milk 2% 4l"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$4.99"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Rye Bread"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$3.99"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;align_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;align_column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is simple yet quite aesthetically pleasing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2Fdee07efcb63f8df006cae598f2b31c38%2Fterminal-table.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2Fdee07efcb63f8df006cae598f2b31c38%2Fterminal-table.png" alt="terminal table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not too shabby!&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;ombre&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, here is a nice little gem created by Aha! director of software engineering, &lt;a href="https://www.aha.io/blog/my-name-is-justin-paulson-this-is-why-i-joined-aha" rel="noopener noreferrer"&gt;Justin Paulson&lt;/a&gt;. &lt;a href="https://github.com/justinpaulson/ombre" rel="noopener noreferrer"&gt;Ombre&lt;/a&gt; provides a fun way to spice up your output by rendering text with a gradient. Here's an example that prints zombie ipsum with a red, white, and blue tint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'ombre'&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Ombre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FF0000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"FFFFFF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0000FF"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="sh"&gt;
**********************************************
* Zombie ipsum brains reversus ab cerebellum *
* viral inferno, brein nam rick mend grimes  *
* malum cerveau cerebro. De carne cerebro    *
* lumbering animata cervello corpora         *
* quaeritis. Summus thalamus brains sit,     *
* morbo basal ganglia vel maleficia?         *
**********************************************
&lt;/span&gt;&lt;span class="no"&gt;TEXT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a colorful output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F1f28a93256a33f9b3f0bed64331a19eb%2Fombre-example.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F1f28a93256a33f9b3f0bed64331a19eb%2Fombre-example.png" alt="Ombre example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use Ombre to display a banner when we launch CLI tools that help us create &lt;a href="https://www.aha.io/engineering/articles/12-tools-for-quality-pull-requests" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F09b8000c1f6092c2384047a4ff8ff246%2Fombre-aha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F09b8000c1f6092c2384047a4ff8ff246%2Fombre-aha.png" alt="ombre aha"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this banner doesn't add any measurable value to our utility, it helps reinforce a sense of team spirit every time it's displayed.&lt;/p&gt;

&lt;p&gt;Be sure to add these new gems to your Ruby command-line arsenal. They have great documentation — and are fun to use — adding a little panache to your command-line utilities.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>devops</category>
      <category>ruby</category>
    </item>
    <item>
      <title>12 Tools for Quality Pull Requests</title>
      <dc:creator>Chris Zempel</dc:creator>
      <pubDate>Thu, 29 Jun 2023 19:12:02 +0000</pubDate>
      <link>https://dev.to/aha/12-tools-for-quality-pull-requests-b67</link>
      <guid>https://dev.to/aha/12-tools-for-quality-pull-requests-b67</guid>
      <description>&lt;p&gt;As you stare into an empty text field, the blinking cursor invites you to engage in a crucial part of being a professional software engineer — writing a pull request. What will you write? Almost every idea you will come up with is downstream of the habits you've cultivated and the information you've consumed. Your attention to your upstream (or lack of) will define your practice — your customary, habitual way of doing something.&lt;/p&gt;

&lt;p&gt;How is your practice of writing pull requests? On one hand, you will improve as you engage in shipping work as part of a team because you'll be repeating the act of writing and reading pull requests. On the other, occasionally applying conscious and deliberate attention to your practice will yield large and rapid improvements. This is when you work on your upstream — the habits, tools, and techniques you lean on when you start authoring a pull request. Often, this is harvesting your experience to see and think in relevant ways you couldn't before. Sometimes, though, the world sees fit to make it really easy on you.&lt;/p&gt;

&lt;p&gt;My teammates recently put out some pull requests which were so nice they (metaphorically) shook me by the shoulders and shouted "you should also be doing this!" Before I can share with you exactly what they did, you must also understand a fuller picture. Sometimes beauty is the lack of something. Appreciating the lack of something is hard if you don't know it is supposed to be there. But when you understand the goals of writing a pull request, it then becomes possible to know which elements you can remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a quality pull request?
&lt;/h2&gt;

&lt;p&gt;Quality means consistently meeting spec. For our pull requests, this means "do I have the right reviewers?", "do reviewers have all the information they need?", and "will someone looking at this in the future be able to access the 'what' and 'why' behind the 'how' of this implementation?"&lt;/p&gt;

&lt;p&gt;Notice these are questions, not advice. Questions are resilient and adaptable to context. Advice is brittle. The best tools to keep in your upstream are the right questions.&lt;/p&gt;

&lt;p&gt;Also notice this has nothing to do with "perfect." While it is tempting to focus on how you can write the perfect pull request, this is the wrong question. A focus on perfectionism is a great way to hide, hedge, and never ship. Or, perhaps more insidious, continually spend lots of wasteful effort when you could instead be learning how to achieve the same effect with less effort.&lt;/p&gt;

&lt;p&gt;The goal is a durable, refined practice you can lean on to write a pull request that meets spec in the shortest amount of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools for writing pull requests
&lt;/h2&gt;

&lt;p&gt;Here is a cheat sheet I use to write quality pull requests, quickly. And below I explain my thought process for each question so you can start using this in your own work.&lt;/p&gt;

&lt;p&gt;&lt;h3 id="cheat-sheet"&gt;#Cheat Sheet&lt;/h3&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  On Writing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Do I have all the right reviewers?&lt;/li&gt;
&lt;li&gt;[ ] Do reviewers have all the information they need?&lt;/li&gt;
&lt;li&gt;[ ] Is my information organized in descending levels of scale?&lt;/li&gt;
&lt;li&gt;[ ] Will someone looking at this in the future be able to access the "what" and "why" behind the "how" of this implementation?&lt;/li&gt;
&lt;li&gt;[ ] Can I clarify my title for other contexts using prefixes, conventions, or other keywords?&lt;/li&gt;
&lt;li&gt;[ ] Is there supplementary information I could add as footnotes?&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Using Details Tags
&lt;/h4&gt;

&lt;p&gt;You can hide nonessential information using &lt;code&gt;details&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;details&amp;gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Toggle me!&lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;Peek a boo!&lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Adding Footnotes
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is a simple footnote[^1]. With some additional text after it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the bottom of your pr:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[^1]: My reference.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  On Structural Artifacts
&lt;/h4&gt;

&lt;p&gt;Should I include some kind of diagrams, illustrations, or screenshots?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ substantially modifies code with a lot of branches of essential complexity&lt;/li&gt;
&lt;li&gt;✅ includes a large amount of discrete before/after states&lt;/li&gt;
&lt;li&gt;💡Integrate this element of your practice earlier into your development to minimize effort&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  On Responding
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Am I coming at this from the right mental frame?&lt;/li&gt;
&lt;li&gt;[ ] Does my feedback block approval?&lt;/li&gt;
&lt;li&gt;[ ] Could this be phrased as a question or observation?&lt;/li&gt;
&lt;li&gt;[ ] Is this a nit?&lt;/li&gt;
&lt;li&gt;[ ] Does my review include something I like?&lt;/li&gt;
&lt;li&gt;[x] If you don't know what to say but you like it, just use ✨.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Going deeper
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Do I have all the right reviewers?
&lt;/h4&gt;

&lt;p&gt;Maybe security, or your platform team should look at this. Perhaps this change will impact another team's functionality, or there's a specific subject matter expert who will be able to tell you information you didn't even know you should think about.&lt;/p&gt;

&lt;h4&gt;
  
  
  Do reviewers have all the information they need?
&lt;/h4&gt;

&lt;p&gt;This is figuring out what information you need to include. One big tell you should mention something is when you're surprised by a piece of information not otherwise indicated in the code. At the least, this information should be present in your pull request. Other information that's usually good to include? The strategies you used to implement, and maybe even the ones you tried and then threw away. Perhaps it is worth explaining the behavior of the system prior to your change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ultimately, how well you do this depends on your wisdom. Wisdom is the ability to correctly predict the future in a specific context. Pay attention to what questions you're asked and what information others seek. Over time, you will learn what you don't need to say. I've noticed that at Aha! we often don't need to include lots of detail on bug fix pull requests. There is the problem, a link to the bug tracker, and a fix contained in the pull request. Unless the fix is nonintuitive, this often adequately expresses all the information a reviewer needs to make an informed decision.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Is my information organized in descending levels of scale?
&lt;/h4&gt;

&lt;p&gt;You've determined what information needs to be present. Now you must determine how to arrange it. This generally means some combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prior system behavior — useful in pull requests addressing bugs with nonobvious fixes&lt;/li&gt;
&lt;li&gt;What the key abstractions are and why they were introduced — provides a mental anchor for your reviewers to understand and contextualize what code they are seeing&lt;/li&gt;
&lt;li&gt;Little big details — is there system behavior, or other finer-scale details which have an impact on the large-scale design of the system? This is worth prioritizing and highlighting earlier on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Will someone looking at this in the future be able to access the "what" and "why" behind the "how" of this implementation?
&lt;/h4&gt;

&lt;p&gt;This presupposes you have a system in which you track your work. In each pull request, we link back to the record which caused the work. Many of us include the record's reference identifier in our commits, and we have an in-house CLI tool which will conveniently branch off wherever we're at and include the reference identifier in our branch name (and thus our default pull request name). Internally, we colloquially refer to these records as "features."&lt;/p&gt;

&lt;p&gt;If you have never had this experience, let me tell you: it is amazing. Being able to look through the contents and history of the shaping feature allows you to understand what was intended by the code change and why the change was introduced. Knowing this has helped unstick me many many times because I am able to confidently distinguish what code we no longer need and what we must keep.&lt;/p&gt;

&lt;p&gt;Instantly recalling context from people I've never met 5+ years ago is just cool. I've started using Aha! Develop for personal work and side projects for access to this form of extended personal/organization recall.&lt;/p&gt;

&lt;p&gt;With these bases covered, let's look at ways you can represent finer scale details.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I represent this detail with a comment?
&lt;/h4&gt;

&lt;p&gt;When there is detail which only makes sense when you're also considering some specific code, include the detail in the code. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The space that needs to be added to text is now added &lt;code&gt;&amp;lt;other place&amp;gt;&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or perhaps you've introduced a finer-scale abstraction or refactor that's only worth calling out the first time a reviewer will reach that line of code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We do if &lt;code&gt;information.fetched? &amp;amp;&amp;amp; information.company_name?&lt;/code&gt; many times in the templates, so I'm adding a convenience method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Can I hide nonessential information using &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; ?
&lt;/h4&gt;

&lt;p&gt;If there are pieces of information in your pull request that aren't essential for the first time someone's reading it, you can hide the information inside a &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt; component. I often use this for quick scripts to set up the system in a certain state, or for deeper explanations of a given topic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;details&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt; How do `details` work? Simple. &lt;span class="nt"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;

  You have your summary, and your details.
&lt;span class="nt"&gt;&amp;lt;/details&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Is there supplementary information I could add as footnotes?
&lt;/h4&gt;

&lt;p&gt;GitHub added support for &lt;a href="https://github.blog/changelog/2021-09-30-footnotes-now-supported-in-markdown-fields/"&gt;markdown&lt;/a&gt; footnotes. This is a great feature that lets you include supplementary information which does not fit well inside the main text.&lt;/p&gt;

&lt;p&gt;Quick reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is a simple footnote[^1]. With some additional text after it.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More advanced use, sourced from the linked GitHub post &lt;br&gt;
  &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--drAq1h1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i0.wp.com/user-images.githubusercontent.com/2503052/135463148-0231966e-8631-41a1-b1a7-66746100d20a.gif%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--drAq1h1h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i0.wp.com/user-images.githubusercontent.com/2503052/135463148-0231966e-8631-41a1-b1a7-66746100d20a.gif%3Fssl%3D1" alt="footnote-cropped" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Should I include some kind of diagram, illustration, or screenshot?
&lt;/h4&gt;

&lt;p&gt;If your pull request substantially modifies code with a lot of branches of essential complexity or includes a large amount of discrete before/after states, consider building some sort of informational aid for your reviewers.&lt;/p&gt;

&lt;p&gt;I've found myself reaching for &lt;a href="https://www.aha.io/notebooks/overview"&gt;whiteboards&lt;/a&gt; on multiple features recently. Whiteboards make it really easy to flowchart and enumerate the intended execution.&lt;/p&gt;

&lt;p&gt;Even just in GitHub-flavored markdown alone, you can create some pretty incredible pull requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B4nxowwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjiklvhhb08k7qpfzmra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B4nxowwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjiklvhhb08k7qpfzmra.png" alt="Diagram, illustration, or screenshot" width="800" height="970"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Putting these together during development while the information is already in your head isn't a lot of extra effort for you. Doing so can produce a tremendous reduction in effort for your reviewers.&lt;/p&gt;

&lt;p&gt;When revisiting older code, a visual cue can quickly remind the reader what this feature looks like, and trigger memories about related context they might have.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I clarify my title for other contexts?
&lt;/h4&gt;

&lt;p&gt;Often your pull request's title will be the main piece of information that others see in systems outside where you keep your code.&lt;/p&gt;

&lt;p&gt;When I have a feature that spans multiple repositories, I often update my titles to include the repo name in brackets up front so it's easy to keep straight.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PIZZA-2022 [pizza-design-system]: upgrade toppings to level Infinity and Beyond&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PIZZA-2022 [pizza-app]: upgrade toppings to level Infinity and Beyond&lt;/code&gt;
(&lt;code&gt;PIZZA-2022&lt;/code&gt; is the format of our Aha! feature reference numbers.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other pieces of information you might want to consider updating with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Include important keywords to help with searching&lt;/li&gt;
&lt;li&gt;Use a consistent convention such as feature ID prefixes&lt;/li&gt;
&lt;li&gt;Name the "what" or "why" instead of the "how"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools for responding to pull requests
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Am I coming at this from the right mental frame?&lt;/strong&gt; It is easy to fall into the emotional trap of over-identifying with your work — causing you to beat yourself up as a failure over each change request. Or maybe you're setting your expectations to a place where change requests will make you feel resentful that you didn't receive approval instead of grateful someone is trying to make your work better.&lt;/p&gt;

&lt;p&gt;Consider that someone else is spending their life and attention understanding your work and helping you move it forward. Often, these are extremely experienced and skilled peers. Think about a time before, when you were newer, when your sole desire was to learn and improve. What would you have done to get this kind of generous feedback? What would you have given? Then think about now. What's actually different? Nothing.&lt;/p&gt;

&lt;p&gt;We're all human. Sometimes there's deadlines or other kinds of pressure. If you find yourself emotionally reactive, don't type anything. Step away. Take a moment and figure out why you're feeling the way you are. Chances are you're being too harsh on yourself. Be kinder to yourself, or reach out for support.&lt;/p&gt;

&lt;p&gt;The goal is consistently shipping quality work with your team.&lt;/p&gt;

&lt;h4&gt;
  
  
  Could this be phrased as a question or observation?
&lt;/h4&gt;

&lt;p&gt;This is a technique I picked up at Aha! which I then realized was pervasively used by effective open source contributors. Once I became cognizant of this technique, I now see it everywhere.&lt;/p&gt;

&lt;p&gt;When you're not sure you have full context, or want to soften your feedback, phrase it as a question. A question might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could we inline these styles instead of modifying our webpack config for just this one place?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where an observation would be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I noticed in my testing that in &lt;code&gt;&amp;lt;some conditions&amp;gt;&lt;/code&gt; I'm still seeing &lt;code&gt;&amp;lt;cookie that shouldn't be there&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Phrasing your feedback in terms of questions or observations is humble and generous. It leaves space for what you might not know and keeps the focus on the work you're doing with your teammates. Not on who was doing it.&lt;/p&gt;

&lt;p&gt;More examples:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the &lt;code&gt;property&lt;/code&gt; relevant at all? Not sure what that distinguishes.&lt;/p&gt;

&lt;p&gt;Can this be private, maybe others too?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason why questions are so effective is they create a conversational loop where you can make progress with someone else. This keeps momentum up and the focus on the work you're doing together. Addressing the question, implementing feedback, clarifying context, etc.&lt;/p&gt;

&lt;p&gt;Such change suggestions can still be submitted with a go-for-launch approval if the code is still going to be improved in the near future. The author can choose whether to address it now, or potentially later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Does my feedback block approval?
&lt;/h4&gt;

&lt;p&gt;If you have feedback that requires change and you're certain it's necessary, use an active voice and explain your reasoning. This could be a simple bug, a small refactor, or a large need. Either way, if you have feedback that will block code approval, make this clear.&lt;/p&gt;

&lt;p&gt;This is a moment when you can be &lt;em&gt;especially&lt;/em&gt; generous and raise the skill level of your team. Here is an example of some particularly good feedback:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's better to test the call one more layer down the stack instead of the private method, as the private method can be refactored now without potentially breaking the dependency on the Tracker layer below.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;tracker_stubbed&lt;/code&gt; config is defined here and just DRYs up the boilerplate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This feedback explains "why" and "what," then provides guidance on "how." I tend to reserve this for situations where I have absolute certainty, the existing implementation is otherwise functional, and I can't easily phrase it as a question.&lt;/p&gt;

&lt;h4&gt;
  
  
  Is this a nit?
&lt;/h4&gt;

&lt;p&gt;A nit is a small, non-blocking and potentially opinionated change suggestion. I find it is best to call these out directly as a nit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;nit: this method seems awfully similar to &lt;code&gt;SomeClass#this_other_method&lt;/code&gt;. Could we use that instead?&lt;/p&gt;

&lt;p&gt;nit: why not &lt;code&gt;!private?&lt;/code&gt; instead of listing every other option?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using language which directly implies this is a nit and maybe we want to do it (at the author's discretion):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Maybe a better way to access this:&lt;br&gt;
&lt;code&gt;&amp;lt;code example&amp;gt;&lt;/code&gt;&lt;br&gt;
I think this will also give you the titleized version, removing the need to call &lt;code&gt;toUpperCase&lt;/code&gt; below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or it is a personal preference:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I prefer to do &lt;code&gt;&amp;lt;thing you're doing&amp;gt;&lt;/code&gt; like this:&lt;br&gt;
&lt;code&gt;&amp;lt;code example&amp;gt;&lt;/code&gt;&lt;br&gt;
Because then you get x, y, and z.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Does my review include something I like?
&lt;/h4&gt;

&lt;p&gt;It's easy to end up only commenting on things to be critical of. Take time to acknowledge the parts that are good, too.&lt;/p&gt;

&lt;p&gt;When you see something you like, say it. If you don't know what to say but you like it, use ✨.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I really like how &lt;code&gt;&amp;lt;thing is working&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Thanks for the well-reasoned approach here&lt;/p&gt;

&lt;p&gt;Those capybara specs 💯&lt;/p&gt;

&lt;p&gt;Nice. Bonus points for meme style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Choose tools to support your purpose
&lt;/h2&gt;

&lt;p&gt;Remember, the goal is a durable, refined practice you can lean on to write a pull request that meets spec in the shortest amount of time.&lt;/p&gt;

&lt;p&gt;Questions are resilient and adaptable to context. Advice is brittle. The best tools to keep in your upstream are questions.&lt;/p&gt;

&lt;p&gt;Here is the cheat sheet&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This blog post only exists because of efforts of other engineers on the team. I stopped multiple times in the middle of reading a pull request with the recognition 'this is so well done.' These moments prompted me to dig in further to understand what specific techniques were causing this reaction in me. If you want to build lovable software with this talented and growing team, &lt;a href="https://www.aha.io/company/careers/current-openings?category=engineering"&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>pullrequest</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Solving a critical bug in the default Rails caching library</title>
      <dc:creator>Andrew Jones</dc:creator>
      <pubDate>Mon, 08 May 2023 21:24:30 +0000</pubDate>
      <link>https://dev.to/aha/solving-a-critical-bug-in-the-default-rails-caching-library-2ga9</link>
      <guid>https://dev.to/aha/solving-a-critical-bug-in-the-default-rails-caching-library-2ga9</guid>
      <description>&lt;h2&gt;
  
  
  An odd coincidence
&lt;/h2&gt;

&lt;p&gt;On March 20th, ChatGPT users &lt;a href="https://old.reddit.com/r/ChatGPT/comments/11wkw5z/has_chatgpt_or_me_been_hacked_ive_never_had_these/"&gt;reported&lt;/a&gt; seeing conversations that were not their own. Just a few weeks earlier I had solved a bug where the &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-memcachestore"&gt;default Rails caching library&lt;/a&gt; (Dalli) would return incorrect values. I thought, "this ChatGPT incident sounds a lot like what could happen with that caching bug via returning incorrect cached HTML." But OpenAI doesn't use Rails so I shrugged the thought off as recency bias.&lt;/p&gt;

&lt;p&gt;My jaw dropped when I saw the &lt;a href="https://openai.com/blog/march-20-chatgpt-outage"&gt;postmortem&lt;/a&gt; — it was exactly the same bug concept, just in a &lt;a href="https://github.com/redis/redis-py"&gt;different library&lt;/a&gt;! A reminder that hard things often transcend particular languages and libraries. And boy, is this a hard bug. It sits at the intersection of caching, shared resource management, and state corruption — infamously tricky problem spaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug mechanism
&lt;/h2&gt;

&lt;p&gt;The Dalli caching client connects to a memcached server with a socket (which is essentially just a buffer, I'll use the terms interchangeably). The client issues a get message to the server which says "give me the value for this key". In response, the server writes some data into the socket buffer. The client then reads bytes off of the socket until it has processed one complete response, making the assumption that what it's reading is the response to its own get message. If the socket was empty at the beginning of the operation, that assumption works and the client returns the correct cache value.&lt;/p&gt;

&lt;p&gt;But what if the buffer was &lt;em&gt;not&lt;/em&gt; empty when the client issued the get command? Then the client processes the data that was on the socket as if it were the requested value, and — since no extra validation steps occur — &lt;strong&gt;returns the wrong value for the given key&lt;/strong&gt;. Worse, because it would only read one value's worth of data out of the buffer (it knows when to stop reading based on a metadata header), the actual response to its request would remain on the socket for the next request to return incorrectly, and so on.&lt;/p&gt;

&lt;p&gt;The socket has thus entered an indefinite "off by one" corrupt state. Meaning the nth get operation will return the value for the n-1th operation's key, leaving its own value in the buffer for the n+1th to read. Oh no! That corrupted state in a different library &lt;a href="https://github.com/redis/redis-py/issues/2624"&gt;explains&lt;/a&gt; ChatGPT's incident.&lt;/p&gt;

&lt;p&gt;If your cache system starts returning the wrong values that's very scary from a security perspective, because data or HTML cached for one user might be shown to another user. In the case I saw firsthand this didn't happen because the incorrect cache values all type mismatched, causing the process to error repeatedly until it was killed. That was lucky, but a Rails app conceivably could see the exact kind of incident OpenAI saw. It would just depend on what type of data was on the corrupted socket and how the application uses the cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it happens
&lt;/h2&gt;

&lt;p&gt;Why would there be an unread value in the buffer? &lt;strong&gt;All that's required is for the client to send a get command to the server and then fail to actually read the response&lt;/strong&gt; — while also leaving the socket around for a future request to use.&lt;/p&gt;

&lt;p&gt;One way this might happen in Dalli is if there's an &lt;a href="https://github.com/petergoldstein/dalli/issues/321"&gt;issue&lt;/a&gt; in &lt;code&gt;get_multi&lt;/code&gt; code. This could occur if the client requests multiple values, reads some subset of those values off of the socket, and then returns before the server finishes writing all of the values to the socket. Another way this might happen is if something - say, an error or a timeout - interrupts the client code execution between issuing a get message and reading the response.&lt;/p&gt;

&lt;p&gt;Ideally the client would discard/reset the socket in case of an error or timeout, and in most scenarios that's exactly what would happen. But all it takes is one code path where the socket can get corrupted and stick around. Unfortunately, there is at least one such a code path, explaining the incorrect caching behavior I observed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The specifics are less important than the question of whether the client should rely completely on the assumption that the socket will be empty when it begins a get operation.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;As a security engineer I love when I can solve a bug deep in an abstraction, making it impossible for a developer using the abstraction to cause the bug again — even if they make a mistake. We should encourage developers to avoid known dangers, sure, but it's best for an abstraction itself to prevent unacceptable outcomes automatically. Or, within the abstraction, we should try to manage our state correctly, but it's best if we can preclude the worst impacts even if there's a flaw and some unexpected state emerges. It is far more robust to make a failure case impossible at the root instead of relying on all the higher level code doing the right thing every time.&lt;/p&gt;

&lt;p&gt;To that end, I pursued a simple foundational fix. Memcached has a &lt;code&gt;getk&lt;/code&gt; &lt;a href="https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#get-get-quietly-get-key-get-key-quietly"&gt;command&lt;/a&gt;, which differs from &lt;code&gt;get&lt;/code&gt; by returning the key alongside the value. The downside is a small performance cost — returning the key means processing more bytes. The upside is that the client gets both the key and the value back from memcached, and can check that the key matches what it asked for.&lt;/p&gt;

&lt;p&gt;That's the &lt;a href="https://github.com/aha-app/dalli/pull/2/files#diff-f7e593880226d650e228c95c83f855fd6d8f9715d9354092faf3c450372f011d"&gt;core of my implementation&lt;/a&gt; - &lt;strong&gt;if the keys don't match, raise an error&lt;/strong&gt;. Simple. Now even if the buffer contains a leftover value for a different key, the client won't return it. Instead it will reset and retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyzing the fix
&lt;/h2&gt;

&lt;p&gt;What of the cost vs benefit? In the context of a multi-tenant Rails application, it is generally not an acceptable risk to potentially expose one tenant's data to another, even in a low probability event. A small increase in overhead to completely rule out a data exposure bug is a small price to pay.&lt;/p&gt;

&lt;p&gt;In this case, the overhead is &lt;em&gt;really&lt;/em&gt; small. When I rolled out this implementation in an application serving millions of requests a day, there was &lt;strong&gt;zero measurable impact on performance&lt;/strong&gt;. Even if the overhead was noticeable, it would still be worthwhile to pay the cost for most threat models.&lt;/p&gt;

&lt;p&gt;It is conceivable that an application using the caching client might make a different cost/benefit calculation and decide that performance is paramount and key:value integrity failures are acceptable. That's why one approach I proposed was making it a configuration option in the upstream PR. Unfortunately the maintainer &lt;a href="https://github.com/petergoldstein/dalli/pull/959#issuecomment-1464167685"&gt;did not agree&lt;/a&gt; that the client should solve this problem at all, despite &lt;a href="https://github.com/petergoldstein/dalli/issues/667"&gt;multiple&lt;/a&gt; other &lt;a href="https://github.com/petergoldstein/dalli/issues/123"&gt;reports&lt;/a&gt; of it happening in production systems.&lt;/p&gt;

&lt;p&gt;The maintainer had some &lt;a href="https://github.com/petergoldstein/dalli/issues/956#issuecomment-1464198149"&gt;valid points&lt;/a&gt; — application layer timeouts in Ruby are &lt;a href="https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/"&gt;dangerous&lt;/a&gt;, and if you're using them, extreme caution is warranted. Consider killing the process entirely after a timeout to prevent state corruption bugs like this one.&lt;/p&gt;

&lt;p&gt;Nonetheless, I firmly believe that given the severity of this issue it is worthwhile for the client itself to prevent the worst impact from happening. I am also not convinced that timeouts are the only way for this bug to happen. See the &lt;a href="https://github.com/petergoldstein/dalli/issues/321"&gt;get_multi&lt;/a&gt; issue or consider the possibility of a future code change introducing a connection handling bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;Another way to solve the problem would be to keep the socket locked until the response is read. That way, a socket with an unread response would not be re-used by a subsequent operation. I'm not sure why Dalli doesn't already work this way, but currently it releases the lock after issuing the get command and re-acquires it before reading the response. I opened a &lt;a href="https://github.com/petergoldstein/dalli/pull/957"&gt;second PR&lt;/a&gt; proposing to keep the lock on the socket for the entire get sequence, which was also rejected.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;safe_get&lt;/code&gt; implementation still has an advantage in that it works regardless of whether the socket is properly handled or even if memcached sends extraneous responses. That approach is &lt;a href="https://github.com/aha-app/dalli/pull/2"&gt;publicly available&lt;/a&gt; and production tested. Please let me know if you have any questions or feedback about it!&lt;/p&gt;

</description>
      <category>security</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Fully remote companies are better than hybrid</title>
      <dc:creator>Jonathan Steel</dc:creator>
      <pubDate>Wed, 01 Feb 2023 18:44:42 +0000</pubDate>
      <link>https://dev.to/aha/fully-remote-companies-are-better-than-hybrid-4f53</link>
      <guid>https://dev.to/aha/fully-remote-companies-are-better-than-hybrid-4f53</guid>
      <description>&lt;p&gt;I have worked in many different environments throughout my career. I have worked in the same building as my teammates and even for a company that had us all in the same room. I have also worked in hybrid environments with a mix of in office and remote employees. In the hybrid case, I was always onsite. I had never been on the other side, where I was remote and there were other people physically together. And in my five years &lt;a href="https://www.aha.io/blog/my-name-is-jonathan-steel-this-is-why-i-joined-aha" rel="noopener noreferrer"&gt;working for Aha!&lt;/a&gt;, I have always been grateful to work for a fully remote company.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I knew I did not want to work in an office anymore and only truly discovered while working at Aha! how great a remote team can be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fully distributed
&lt;/h2&gt;

&lt;p&gt;Frequent communication and collaboration are invaluable to every group at a company. As an engineering team lead, I know this doesn't come as naturally to some as it does to others. Engineering team members can be especially susceptible to losing contact with the rest of the team and company when they are in a hybrid situation.&lt;/p&gt;

&lt;p&gt;When some people are in person and others are remote it is harder for everyone to succeed equally. Some teams may see the creative work, decision-making, and architecture being discussed without the remote engineer that will do the actual work. Those decisions are encoded in the form of features, tickets, or tasks that flow down to the engineer. If there is any confusion in the work assignments, it may be perceived as a communication error on the part of the remote engineer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When everyone is remote, there are no communication barriers. We are all accustomed to jumping on a video call or sending a quick instant message.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Onsites
&lt;/h2&gt;

&lt;p&gt;Twice a year, our entire company gets together in person for a week at a destination travel location that we call our company onsite. Everyone has an amazing time discussing the product roadmap, meeting with teammates, volunteering through our Aha! Cares program, and enjoying great meals together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlmt4uua8k3y5twix905.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlmt4uua8k3y5twix905.jpeg" alt="Onsite"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In June 2022, Aha! was able to host an in-person onsite for the first time in over two years. Due to everyone's different circumstances, not everyone was able to attend, including me. We have a truly exceptional team that coordinates these events to include everyone as much as possible — whether onsite or offsite. But I was worried what that might look like.&lt;/p&gt;

&lt;p&gt;So what was it like? At Aha!, we go above and beyond with everything we do and this event was no exception. Everyone tried their hardest to make sure we felt included. We even had our own events that were just as memorable as those at the previous offsite.&lt;/p&gt;

&lt;p&gt;But hybrid work events are just not the same. Sometimes it was hard to hear side conversations and the random comments thrown out in the audience that caused a ripple of laughter.&lt;/p&gt;

&lt;p&gt;The positive takeaway from the experience was that it crystallized in my mind the idea that I never want to work for a hybrid company. I don't think hybrid environments can ever work without leaving some team members feeling left out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This onsite (when I was offsite) had me feeling left out in a way that I didn't when we were all in person or all remote.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some aspects of hybrid environments may improve over time. Technology, tools, and processes can always get better. As hybrid environments become more common, people may also become increasingly familiar and comfortable working with remote and colocated team members at the same time. Even with all those improvements, there is no way to include a remote employee in the random dinner a team might share together.&lt;/p&gt;

&lt;p&gt;I am lucky that Aha! will always make a great effort to include each and every team member. And I now know for certain that hybrid will never be as good as fully remote or fully in-person. My &lt;a href="https://www.aha.io/company/team" rel="noopener noreferrer"&gt;teammates at Aha!&lt;/a&gt; are all so passionate and motivated to bring their best to work every day — 100% remote is what works for us. I am more excited than ever to work for a fully remote company and I am especially excited for our next company onsite.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are challenged to do great work and have fun doing it. If you want to build lovable software with this talented and growing team, &lt;a href="https://www.aha.io/company/careers/current-openings?category=engineering" rel="noopener noreferrer"&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>remote</category>
      <category>hiring</category>
    </item>
    <item>
      <title>Building a composable query generator for GraphQL</title>
      <dc:creator>Justin Weiss</dc:creator>
      <pubDate>Fri, 13 Jan 2023 18:15:56 +0000</pubDate>
      <link>https://dev.to/aha/building-a-composable-query-generator-for-graphql-36mn</link>
      <guid>https://dev.to/aha/building-a-composable-query-generator-for-graphql-36mn</guid>
      <description>&lt;p&gt;In a lot of newer projects, we use our &lt;a href="https://www.aha.io/support/develop/develop/extensions/graphql-api"&gt;GraphQL API&lt;/a&gt;. This is the same API you use when you're building Aha! Develop extensions. GraphQL has given us a more consistent, more flexible, and often more efficient way to access the data inside an Aha! account.&lt;/p&gt;

&lt;p&gt;GraphQL queries are just strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
  feature(id: "DEMO-29") {
    id
    name
  }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/graphql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strings are easy to start with and work well for simple, mostly static queries. But what about something more complex? What if you wanted the name of the person assigned to each feature but not when you're looking at a list of your own features? What if only some teams used sprints and you didn't want sprint information if a team didn't use them? How could you easily make wildly customizable views and only fetch the data you needed for that specific view?&lt;/p&gt;

&lt;p&gt;Simple interpolation is one option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sprintQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sprintsEnabled&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`{
  sprint {
    id
    name
    startDate
    duration
  }
}`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
  features {
    id
    name
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;sprintQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this can quickly get out of hand. You need to know in advance how a query can be modified so you can be sure to leave room for strings to be interpolated inside of them. Handling optional arguments can be a pain. So if strings aren't the right option for very dynamic queries, what is?&lt;/p&gt;

&lt;h2&gt;
  
  
  The next step beyond strings
&lt;/h2&gt;

&lt;p&gt;There's a common solution to this problem: Store the information you need to make your final decisions in a more raw form that you can look at. Keep them in a structure — an introspectable form — until you're ready to generate a result.&lt;/p&gt;

&lt;p&gt;What does that look like, though?&lt;/p&gt;

&lt;p&gt;Say a part of your code wanted a few fields from a Feature API, "id", and "name". Some other code wants the reference number too. Instead of building a query as a string and interpolating fields into it, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
  features {
    id
    name
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;maybeReferenceNumber&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could keep a list of fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other parts of your system could add to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;referenceNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you generate your query, the query has all of the information it needs right at hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`{
  features {
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  }
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems like a minor change, and it is, but it's powerful. An object, which is easy to work with, keeps track of the decisions you've made up to this point. Your query generation is simple — it just does what the object tells it to do. Here, that's turning an array of names into fields in the query. This opens up a lot of possibilities but first we can clean it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to store the query state
&lt;/h2&gt;

&lt;p&gt;If you've ever used Rails, you'll know that it's great at building queries over time. You call methods to build up a query and only fire it off when you're done with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;project_id: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;release_id: &lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;release&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've been surprised that more languages or frameworks haven't been influenced by this query building. To me, it's one of the best parts of Rails.&lt;/p&gt;

&lt;p&gt;You can build something like this with GraphQL, though, keeping your decisions in an object and generating a query at the last minute. Imagine a Query class. We'll keep it simple to start, just holding a query type and a list of fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you can implement a simple "select" method to select ID and name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pass that query object around and other functions can add to that query object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;referenceNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when you need it, the Query object can generate a query string. Just like the example above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`{
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; {
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      }
    }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have this string, you can hand it to whichever GraphQL library you're using as if you wrote it yourself.&lt;/p&gt;

&lt;p&gt;With that simple pattern, you can go further. You can add arguments to filter the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;processFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Very basic conversion, can be improved as necessary&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`(filters: {&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;})`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`{
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}${&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; {
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      }
    }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;assigneeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add subqueries to select information from child objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subqueries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subquery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subqueries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subquery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;asSubquery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`(filters: {&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;})`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subqueryString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; {
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subqueries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;asSubquery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
      }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asSubquery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;subqueryString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`{
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subqueryString&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you can build arbitrarily complicated queries based on whatever state you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;features&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;assigneeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12345&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;includeRequirements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;requirementsQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;requirements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;requirementsQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requirementsQuery&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on, any time you want to change a query based on information you have someplace else, it's as easy as calling a method.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;This query generator is simple and is still missing a lot, but the core idea is easy to extend to fit your API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can add functionality for sorting, passing query arguments, pagination, etc.&lt;/li&gt;
&lt;li&gt;You can have it generate GraphQL fragments and variables instead of a single string.&lt;/li&gt;
&lt;li&gt;You can add another layer. For example, have your query framework generate Apollo documents instead of strings and you can get better editor integration and error messages.&lt;/li&gt;
&lt;li&gt;You can create a mutation builder, tracking changes you make to your JavaScript objects and generating mutations based on those changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We've done all of those things, and found a lot of value in a flexible generator like this. Building a query generator ourselves also allowed us to integrate it more closely with other parts of our application framework. It's a nice simple base we could build off of easily.&lt;/p&gt;

&lt;p&gt;And once you have this base — an introspectable object that can generate the strings you need — your code no longer needs to worry about the actual queries. It's just responsible for the decision-making and the generator does the rest.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Aha! is happy, healthy, and hiring. Join us!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We are challenged to do great work and have fun doing it. If you want to build lovable software with this talented and growing team, &lt;a href="https://www.aha.io/company/careers/current-openings?category=engineering"&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Using Capybara to test responsive code</title>
      <dc:creator>Will Lawrence</dc:creator>
      <pubDate>Fri, 11 Nov 2022 20:41:21 +0000</pubDate>
      <link>https://dev.to/aha/using-capybara-to-test-responsive-code-4a96</link>
      <guid>https://dev.to/aha/using-capybara-to-test-responsive-code-4a96</guid>
      <description>&lt;p&gt;As more users opt for mobile browsing, &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design"&gt;responsive design&lt;/a&gt; becomes more important — even for applications that are primarily used on a desktop. Responsive design is a key consideration for Aha! Ideas because it's convenient to answer polls and submit ideas using mobile devices. Usually the application behavior between mobile and desktop are the same, just rearranged on the page. But what happens when application behavior is different between devices with different-sized screens? Automated tests are necessary to ensure these different screen behaviors perform as expected in their environments.&lt;/p&gt;

&lt;p&gt;Engineering at Aha! focuses on using and improving &lt;a href="https://github.com/teamcapybara/capybara"&gt;the Capybara test framework&lt;/a&gt;. We have added many helpers and additional functionality to make working with Capybara easy. Testing at mobile widths is another chance to improve our testing tooling. Here is the incremental approach that we used to add mobile testing helpers.&lt;/p&gt;

&lt;h2&gt;
  
  
  First pass
&lt;/h2&gt;

&lt;p&gt;Below, we have an example test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"mobile user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"user logs in on mobile"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# These labels are specific to mobile widths&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Last name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test will fail because we aren't resizing to mobile width, so our name labels won't appear. Let's fix that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"mobile user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# resize to mobile width&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;764&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"user logs in on mobile"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# These labels are specific to mobile widths&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Last name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an acceptable first pass. Our page is indeed resized for mobile testing. However, it presents three problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It doesn't tell us why we are resizing. The test has that information but it would not be clear out of the test.&lt;/li&gt;
&lt;li&gt;This code isn't particularly reusable because it requires some insight into how Capybara works.&lt;/li&gt;
&lt;li&gt;The test window remains resized for all subsequent tests. This is a subtle problem that will lead to unexpected and intermittent test failures.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using a helper
&lt;/h2&gt;

&lt;p&gt;Let's see how we can fix those three problems. Below is a helper we can use to resize for mobile testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in spec/support/capybara_helpers.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;with_mobile_width&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;764&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# resize to mobile&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="c1"&gt;# run the test&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# resize back to default&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then in our test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"mobile user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"user logs in on mobile"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;with_mobile_width&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
       &lt;span class="c1"&gt;# These labels are specific to mobile widths&lt;/span&gt;
       &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Last name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution works better. It can be reused, it tells us why we are resizing, and it won't interfere with other tests. But there is still room for improvement.&lt;/p&gt;

&lt;p&gt;We are adding another layering of nesting to the test. If we continue to use it in other places, then we are going to have some deeply nested tests. There is another way we can get this functionality without using this helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating our configuration
&lt;/h2&gt;

&lt;p&gt;I want to be able to drop a &lt;code&gt;:mobile_width&lt;/code&gt; in our test declaration. This resolves our three problems and won't clutter the tests. Below is an update to our configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# in spec/support/capybara.rb&lt;/span&gt;
&lt;span class="n"&gt;rspec_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;browser&lt;/span&gt;

  &lt;span class="c1"&gt;# if we have :mobile in test, resize. Otherwise go to default.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:mobile_width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;375&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;764&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using the &lt;code&gt;before(:each)&lt;/code&gt; hook. This does exactly what it says on the box. Before each test runs, hook into it and perform an action. In this case, we are checking to see if the test has a &lt;code&gt;:mobile_width&lt;/code&gt; key. If it does, run at a mobile width. If it does not, run at the standard width. The test looks a lot cleaner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"mobile user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:mobile_width&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"user logs in on mobile"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# These labels are specific to mobile widths&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Last name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the tests in the mobile user context are now going to run at that width. We have addressed the original three issues. This is reusable, it explains what we are doing to the test, and the mobile width won't leak into other tests. We also don't need to add another layer of testing to our code like we did by adding the &lt;code&gt;with_mobile_width&lt;/code&gt; helper, so the tests remain tidy and sustainable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Aha! is happy, healthy, and hiring. Join us!
&lt;/h3&gt;

&lt;p&gt;This feature took a lot of tinkering and guidance from other engineers. Without such a knowledgeable and helpful team, I would not have arrived at this clean solution. If you want to build lovable software with this talented and growing team, &lt;a href="https://www.aha.io/company/careers/current-openings?category=engineering"&gt;apply to an open role&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>testing</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Writing and testing a custom RuboCop cop</title>
      <dc:creator>Kyle d'Oliveira</dc:creator>
      <pubDate>Fri, 21 Oct 2022 17:36:44 +0000</pubDate>
      <link>https://dev.to/aha/writing-and-testing-a-custom-rubocop-cop-32bc</link>
      <guid>https://dev.to/aha/writing-and-testing-a-custom-rubocop-cop-32bc</guid>
      <description>&lt;p&gt;Solving a problem is great — but keeping it from coming back is even better. As we resolve issues in our code base, we often consider how to keep that classification of issue out of the code base entirely. Sometimes we reach for &lt;a href="https://docs.rubocop.org/rubocop/index.html"&gt;RuboCop&lt;/a&gt; to help us police certain patterns. This also helps to document the originating issue and educates teammates on why these patterns are undesirable.&lt;/p&gt;

&lt;p&gt;RuboCop is more than just a linter. It is highly extensible and allows you to write &lt;a href="https://thoughtbot.com/blog/rubocop-custom-cops-for-custom-needs"&gt;custom cops&lt;/a&gt; to enforce specific behavior. These cops can be used to create better code practices, prevent bad patterns from sneaking into a legacy code base, and provide training for other engineers. But it can be tricky to know &lt;a href="https://docs.rubocop.org/rubocop/development.html"&gt;how to create a new cop&lt;/a&gt; and if it will work long-term.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We can write unit tests to ensure the success of our custom cops, just as we would with any application code.&lt;br&gt;
Let's explore this with an example to show how testing could be done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing custom cops
&lt;/h2&gt;

&lt;p&gt;With the Aha! engineering team, every model has an &lt;code&gt;account_id&lt;/code&gt; attribute present and for &lt;a href="https://brakemanscanner.org/docs/warning_types/mass_assignment/"&gt;security reasons&lt;/a&gt;, we never want this to be set via mass-assignment. To avoid this, we want to prevent certain attributes from being added to &lt;a href="https://apidock.com/rails/ActiveRecord/Base/attr_accessible/class"&gt;attr_accessible&lt;/a&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bad&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;
  &lt;span class="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;account_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# good&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;
  &lt;span class="n"&gt;attr_accessible&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a custom cop that analyzes the arguments to that method and will error if any protected attribute is present. The custom cop we have ends up looking something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RuboCop::Cop::ProtectedAttrAccessibleFields&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;
  &lt;span class="c1"&gt;# We can define a list of attributes we want to protect&lt;/span&gt;
  &lt;span class="no"&gt;PROTECTED_ATTRIBUTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
  &lt;span class="c1"&gt;# We can define an error message that is displayed when an offense is detected.&lt;/span&gt;
  &lt;span class="c1"&gt;# This can be helpful to communicate information back to other engineers&lt;/span&gt;
  &lt;span class="no"&gt;ERROR_MESSAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="sh"&gt;
    Only permit attributes that are safe to be completely user controlled. Typically any *_id field could be problematic.
    Instead perform direct assignment of the field after doing a scoped lookup. This is the safest way to handle user input.
    Some fields such as &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;PROTECTED_ATTRIBUTES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; should never be used as part of attr_accessible.
&lt;/span&gt;&lt;span class="no"&gt;  ERROR&lt;/span&gt;
  &lt;span class="c1"&gt;# We want to examine method calls. Particularly those that are calling the attr_accessible method&lt;/span&gt;
  &lt;span class="c1"&gt;# and also have arguments we care about&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;receiver_attr_accessible?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;protected_arguments?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;# If we do detect an attr_accessible call with arguments we care about, we can record an offense&lt;/span&gt;
      &lt;span class="n"&gt;add_offense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="no"&gt;ERROR_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="kp"&gt;private&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receiver_attr_accessible?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;method_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="ss"&gt;:attr_accessible&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;protected_arguments?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sym_type?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;str_type?&lt;/span&gt;
        &lt;span class="no"&gt;PROTECTED_ATTRIBUTES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This custom cop does the trick. Adding a test for it ensures that it won't break in the future when we update RuboCop or extend the functionality. In order to write a test, we need to understand how the custom cops are set up and run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instantiate a custom cop
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;RuboCop::Cop::Cop&lt;/code&gt; inherits from &lt;code&gt;RuboCop::Cop::Base&lt;/code&gt; and that allows the instantiation without &lt;a href="https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/base.rb#L71"&gt;any arguments&lt;/a&gt;. So it turns out this isn't anything special — creating a new instance of our cop is really as simple as: &lt;code&gt;RuboCop::Cop::ProtectedAttrAccessibleFields.new&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If the cop requires some kind of configuration, it can be passed to the instance via a &lt;code&gt;RuboCop::Config&lt;/code&gt; object. The &lt;code&gt;RuboCop::Config&lt;/code&gt; takes two arguments. RuboCop can &lt;a href="https://docs.rubocop.org/rubocop/configuration.html"&gt;provide configuration&lt;/a&gt; via YML files. You can use the first argument of &lt;code&gt;RuboCop::Config&lt;/code&gt; to pass this configuration with various values from the test. The second argument is the path of the loaded YML file, which can be ignored in the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ProtectedAttrAccessibleFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Process, execute, examine
&lt;/h2&gt;

&lt;p&gt;As it turns out, there is a &lt;a href="https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/base.rb#L238"&gt;method&lt;/a&gt; available, &lt;code&gt;RuboCop::Cop::Base#parse&lt;/code&gt; , that accepts a string as input and will return something the cop can process.&lt;/p&gt;

&lt;p&gt;This allows us to have something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;CODE&lt;/span&gt;&lt;span class="sh"&gt;
  attr_accessible :account_id
&lt;/span&gt;&lt;span class="no"&gt;CODE&lt;/span&gt;
&lt;span class="n"&gt;processed_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a class from within RuboCop, &lt;code&gt;RuboCop::Cop::Commissioner&lt;/code&gt; , that is responsible for taking a &lt;a href="https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L44"&gt;list of cops&lt;/a&gt; and using those to &lt;a href="https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L79"&gt;investigate&lt;/a&gt; the processed source code. In order to run our cop, we can run this method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;commissioner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;investigation_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;investigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed_source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RuboCop::Cop::Commissioner#investigate&lt;/code&gt; method will return an instance of &lt;a href="https://github.com/rubocop/rubocop/blob/d8c2cd0d891c9e49f528041d3b0758a6fa480265/lib/rubocop/cop/commissioner.rb#L18"&gt;RuboCop::Cop::Commissioner::InvestigationReport&lt;/a&gt; which is a simple struct class that has a list of offenses that have been recorded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it all together
&lt;/h2&gt;

&lt;p&gt;We end up with a test file that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ProtectedAttrAccessibleFields&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:cop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:commissioner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"records an offense if we use allow account_id as a string"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;CODE&lt;/span&gt;&lt;span class="sh"&gt;
      attr_accessible :foo, 'account_id'
&lt;/span&gt;&lt;span class="no"&gt;    CODE&lt;/span&gt;
    &lt;span class="n"&gt;investigation_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;investigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;investigation_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offenses&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_not&lt;/span&gt; &lt;span class="n"&gt;be_blank&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;investigation_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offenses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ERROR_MESSAGE&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"records an offense if we use allow account_id as symbol"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;CODE&lt;/span&gt;&lt;span class="sh"&gt;
      attr_accessible :foo, :account_id
&lt;/span&gt;&lt;span class="no"&gt;    CODE&lt;/span&gt;
    &lt;span class="n"&gt;investigation_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;investigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;investigation_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offenses&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_not&lt;/span&gt; &lt;span class="n"&gt;be_blank&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;investigation_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offenses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eql&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ERROR_MESSAGE&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"doesn't record an offense if no protected attribute is used"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;CODE&lt;/span&gt;&lt;span class="sh"&gt;
      attr_accessible :foo
&lt;/span&gt;&lt;span class="no"&gt;    CODE&lt;/span&gt;
    &lt;span class="n"&gt;investigation_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;commissioner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;investigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;investigation_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;offenses&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_blank&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we know how to write tests, we can use them as a starting point for building new cops, extending existing cops, and ensuring that things continue to function as our application grows and evolves. These little investments into &lt;a href="https://evilmartians.com/chronicles/custom-cops-for-rubocop-an-emergency-service-for-your-codebase"&gt;project-specific cops&lt;/a&gt; can end up being a large investment in the future health of the projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href="https://www.aha.io/suite-overview"&gt;product development&lt;/a&gt; approach, use &lt;a href="https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop"&gt;Aha! Roadmaps and Aha! Develop together&lt;/a&gt;. Sign up for a &lt;a href="https://www.aha.io/trial"&gt;free 30-day trial&lt;/a&gt; or &lt;a href="https://www.aha.io/live-demo"&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it. &lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>rubocop</category>
    </item>
    <item>
      <title>Service layer for business logic — Organizing code in a Rails monolith</title>
      <dc:creator>Jonathan Steel</dc:creator>
      <pubDate>Fri, 14 Oct 2022 19:10:30 +0000</pubDate>
      <link>https://dev.to/aha/service-layer-for-business-logic-organizing-code-in-a-rails-monolith-40cj</link>
      <guid>https://dev.to/aha/service-layer-for-business-logic-organizing-code-in-a-rails-monolith-40cj</guid>
      <description>&lt;p&gt;Our engineering team builds the Aha! suite using a Rails monolith. We carefully weighed &lt;a href="https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha"&gt;a number of options&lt;/a&gt; before determining that this would provide the most lovable solution for our users and our team. But the discussion does not end with choosing this path for our code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We work hard to build the best monolith possible so we can retain a high velocity and a clean code base.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the foundations of having a successful monolith is to ensure it is well-organized. Having a service layer for business logic can help keep Ruby on Rails code from growing out of control. Whichever framework you use, it will only help organize the common elements. Design a system for organizing your business logic code using well-defined, unambiguous patterns. Read on to learn how we did this for the Aha! suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing business logic
&lt;/h2&gt;

&lt;p&gt;Rails does a great job of giving you a place to put just about any kind of code. When you create a new Rails project, it comes with an elegant predefined &lt;a href="https://guides.rubyonrails.org/getting_started.html#creating-a-new-rails-project-installing-rails"&gt;directory structure&lt;/a&gt;. There is a place for the models, views, controllers, tests, assets, database migrations, and much more. It does have its limitations though. The most common issue that Rails projects run into is how to deal with business logic.&lt;/p&gt;

&lt;p&gt;When you look at tutorials of Rails code, they are usually beautiful, concise, simple, and easy to read. Many real-life projects start out this way as well. But over time, more and more business logic gets peppered into that clean, elegant code. You cannot avoid this business logic because you would not have a program without it. So as your Rails program grows and ages, all these clean areas of code will start to grow and look less and less like the ideal "Hello, world" examples. The question then quickly arises of where this code should go.&lt;/p&gt;

&lt;p&gt;Often this complex business logic will start to collect in the controllers. A very common practice is to push that logic into the models in an attempt to keep the complexity in one spot. The size of your models is going to grow proportionally with the size of your application. Combining all your complicated and most frequently used logic together into a single location that never stops growing is a disaster waiting to happen.  This will start to get painful as your application becomes a monolith and the code becomes unreadable.&lt;/p&gt;

&lt;p&gt;Does Rails have a place for business logic? The &lt;a href="https://guides.rubyonrails.org/v5.2/getting_started.html"&gt;Rails guides&lt;/a&gt; mention the lib directory as a place for extended modules for your application. You'll find some examples of pushing complex code into concerns, but most concerns are for code reuse. That code is still being included in the same places we said not to put the business logic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never force code into a framework if there is no clear place for it — you may have to venture off the Rails instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Business logic attracts complexity and tends to change more frequently than other areas of your code. This creates the perfect breeding ground for bugs and regressions. Any solution should isolate the business logic, making it easier to test and accelerate future changes. When file size increases proportionally with the application size, your code is destined to be hard to read, understand, and modify. The best way to prevent this is to create code that is narrowly focused instead of serving multiple purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a service layer
&lt;/h2&gt;

&lt;p&gt;One concrete solution is to create a service layer composed of small objects that serve specific use cases. A service layer for business logic can help keep Ruby on Rails code from growing out of control. Whether you use Ruby on Rails or another language and framework, that framework will only help organize common elements. Take a cue from your framework and design a system for organizing your business logic code using well-defined, unambiguous patterns.&lt;/p&gt;

&lt;p&gt;A common place to create a service later is in app/services. Each object will usually only serve a single purpose or possibly a few very tightly related purposes. Keeping these objects focused and constrained to a single purpose ensures that no matter how complex your monolith gets, these objects will never get too big. Here is an example of a simple service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ScheduledReportRunner&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_scheduled_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;allowed_to_send_report?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;reply_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_reply_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;ScheduledReportMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduled_report_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reply_to&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

    &lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next_run&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_frequency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
    &lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allowed_to_send_report?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_reply_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class is small and easy to understand, use, and test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;ScheduledReportRunner&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#send_scheduled_report"&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"delivers a scheduled report mail message"&lt;/span&gt;
      &lt;span class="n"&gt;scheduled_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ScheduledReportMailer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report_message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"support@aha.io"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_call_original&lt;/span&gt;
      &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_scheduled_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"does not send a report that is not allowed to go out"&lt;/span&gt;
      &lt;span class="n"&gt;scheduled_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last_sent_at: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ScheduledReportMailer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_not&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_scheduled_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"schedules the next run of the report"&lt;/span&gt;
      &lt;span class="n"&gt;scheduled_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;send_frequency: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_scheduled_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next_run&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_now&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"uses a custom reply_to when necessary"&lt;/span&gt;
      &lt;span class="n"&gt;scheduled_report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;last_sent_at: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;reply_to: &lt;/span&gt;&lt;span class="s2"&gt;"fred@aha.io"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ScheduledReportMailer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:scheduled_report_message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"fred@aha.io"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_call_original&lt;/span&gt;
      &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_scheduled_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is often useful to return rich domain objects specific to that service object. For example, instead of returning a single value, you can return an object that has a status, error codes, and multiple model objects. That object can then be passed around as a single unit that shows the correlation between the various items inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organizing with a service layer
&lt;/h2&gt;

&lt;p&gt;In a controller, you can wrap all related business logic together into a single object, take the return value, and pass that on to your views. The rich return objects often eliminate the need to have multiple instance variables instantiated and drilled down through your views. Helper code can now be stored in a more appropriate place that is usable by parts of the code other than views.&lt;/p&gt;

&lt;p&gt;Jobs and rake tasks often end up getting pretty complex as well. These should be very thin wrappers that parse arguments and then delegate to a service object. Structuring your code in this way makes it easy to call from the console if somebody wants to run a rake task or job manually. Having small classes also helps immensely with &lt;a href="https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith"&gt;testing a monolith&lt;/a&gt;. By simplifying other areas of your application like models, controllers, and jobs, they now become much easier to test as well.&lt;/p&gt;

&lt;p&gt;The service layer also provides isolation from the rest of your Rails code. Isolation is desirable to provide room for experimentation. You can use whatever coding style you want to create these objects without enforcing that structure on other parts of the code. You can use plain old Ruby objects, classic object-oriented inheritance hierarchies, object composition, meta programming, or even some esoteric bash script you wrote years ago. I prefer small and simple plain old Ruby objects, which are easy to follow.&lt;/p&gt;

&lt;p&gt;It also does not matter how you organize files within the service layer, especially at first. As your service layer grows, patterns will emerge and you can reorganize by creating subdirectories and moving files around. But no matter how big or organized this code gets, you can always create a new subdirectory and try out a new experimental approach there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping a Minimum Lovable Product
&lt;/h2&gt;

&lt;p&gt;Using a service layer helps us to align our work with our team and company goals. We always strive for the &lt;a href="https://www.aha.io/roadmapping/guide/plans/what-is-a-minimum-lovable-product"&gt;Minimum Lovable Product (MLP)&lt;/a&gt;, only shipping features when they are ready from a technical standpoint. An MLP is an initial offering that users love from the start. It should also be loved by the engineers that are working to maintain it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Features cannot just look good on the frontend — they should be well-organized in the background as well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The service layer helps you ship strong code the first time. When you are first authoring a feature, it is very simple to take a chunk of code and create a service object out of it. This means you are more likely to do that right from the start instead of waiting to refactor at a later date. If you do need to refactor, it will mean shuffling around code in small isolated objects and files. That is much easier than coming back after your feature is ready and trying to collect up all the logic you sprinkled around in models, controllers, and views. The service layer is so easy to use that you will feel guilty pushing up a pull request with code where it should not be.&lt;/p&gt;

&lt;p&gt;If used properly, the service layer will help keep your Rails code looking like the simple examples of the Rails guides. It helps to &lt;a href="https://www.aha.io/engineering/articles//2022-09-02-technical-debt-technically-not-debt"&gt;avoid technical debt&lt;/a&gt; and allows Rails to accomplish what it was built for. It also helps reduce the complexity of implementing, maintaining, and testing business logic. It will help you deliver great features with your growing monolith and keep you happing while doing it.&lt;/p&gt;

&lt;p&gt;We will continue to evolve our monolith and make alterations to the architecture to meet our growing needs. Read our other posts on how we chose to utilize a monolith for the Aha! suite:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith"&gt;From One, Many — Building a Product Suite With a Monolith&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.aha.io/engineering/articles/embrace-the-monolith-adding-a-new-product-to-aha"&gt;Embrace the monolith: Adding a new product to Aha!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href="https://www.aha.io/suite-overview"&gt;product development&lt;/a&gt; approach, use &lt;a href="https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop"&gt;Aha! Roadmaps and Aha! Develop together&lt;/a&gt;. Sign up for a &lt;a href="https://www.aha.io/trial"&gt;free 30-day trial&lt;/a&gt; or &lt;a href="https://www.aha.io/live-demo"&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>hiring</category>
      <category>onboarding</category>
    </item>
    <item>
      <title>Platform Engineering vs. Site Reliability Engineering</title>
      <dc:creator>Alexander Bartlow</dc:creator>
      <pubDate>Wed, 28 Sep 2022 17:33:52 +0000</pubDate>
      <link>https://dev.to/aha/platform-engineering-vs-site-reliability-engineering-3ei5</link>
      <guid>https://dev.to/aha/platform-engineering-vs-site-reliability-engineering-3ei5</guid>
      <description>&lt;p&gt;Striker and goalie. Offense and defense. Deploy and recalibrate. Many disciplines have dichotomy between the tasks that accomplish a goal and tasks that protect the ability to do so. Delivering features, building out user flows, and optimizing conversion rates constitute the "offense" of many software companies. All of these activities are directly tied to the goal of growing the business. Defensive tasks encompass security, operations, and disaster recovery. These tasks are designed to prevent loss and empower the offense.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An engineering team cannot run optimal code or move the business forward with a weak infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the software world, this alliance is realized through the engineering and operations teams. Some organizations refer to these as the "profit-center" and "cost-center," respectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional site reliability engineering
&lt;/h2&gt;

&lt;p&gt;Though symbiotic, the relationship between engineering and operations may encounter pitfalls at more complex organizations. The introduction to Google's &lt;a href="https://sre.google/sre-book/introduction/" rel="noopener noreferrer"&gt;Site Reliability Engineering: How Google Runs Production Systems&lt;/a&gt; describes how the two groups may have varied goals, backgrounds, skill sets, incentives, vocabulary, and assumptions. And when left unchecked, strife can form as organizational effectiveness breaks down.&lt;/p&gt;

&lt;p&gt;Google's solution to this problem is the discipline of site reliability engineering (SRE). Developers are tasked with writing software that creates a stable environment for the deployment and operation of the infrastructure. Site reliability engineers at Google work with development teams on a sort of contract — SRE runs the application only if development follows the practices needed to make the application run efficiently. If the development team does not hold up their end of the bargain, then the SRE will decline to hold up theirs and work with teams who do.&lt;/p&gt;

&lt;p&gt;This works well for Google, but turning down work from one part of our engineering team is not an option for the Aha! team. We have no site reliability engineers on staff at Aha! — because we found a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unified engineering goals
&lt;/h2&gt;

&lt;p&gt;We solved the problem of the development-operations split by defining clear goals for our dedicated platform team. Our platform team has goals similar to a traditional operations team, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operability — How do we run the system on a day-to-day basis and make routine changes?&lt;/li&gt;
&lt;li&gt;Reliability — How do we ensure the system is available for our customers?&lt;/li&gt;
&lt;li&gt;Resilience  — When the system is unavailable, how do we get it running again?&lt;/li&gt;
&lt;li&gt;Observability — How do we ensure that we know before our customers when the system needs help?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have also adopted six additional goals to help us create a happy, effective, and high-performing engineering team:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inverse toil&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sre.google/sre-book/eliminating-toil/#:~:text=Toil%20defined" rel="noopener noreferrer"&gt;Toil&lt;/a&gt; refers to the time engineers spend doing repetitive work that doesn't deliver real value to customers. While the amount of manual work the platform team needs to do to operate production is higher than the rest of the engineering team, we still try to keep our time spent toiling to a minimum. For most of our platform engineers, it's less than 20% of their time. Google's SREs target 50% or less time spent toiling, so we believe that our investments in automation have been very effective.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Release velocity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How long does it take a pull request to be deployed after being flagged as ready? While we attempt to optimize the time taken in continuous integration, test automation is not a core focus for us. Our developers closest to the code are best suited to test it, so we leave that responsibility to them. We instead focus on the deployment pipeline — building the core images and getting them into production. Simplifying that process allows it to be done many times a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition to deployment speed, some changes require a gradual rollout or an experimental stage to validate the changes we made. The platform team manages the feature flag functionality at Aha! and continues to extend it to provide support for more use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer productivity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The platform team maintains a &lt;a href="https://www.aha.io/engineering/articles/log-management-using-aws-athena" rel="noopener noreferrer"&gt;robust logging system&lt;/a&gt; to allow our engineers to troubleshoot problems in production quickly and effectively without direct access to customer data. We maintain the Docker setup for local development to give engineers a stable way to run the application and all of its dependencies. Our &lt;a href="https://www.aha.io/engineering/articles/dynamic-stagings" rel="noopener noreferrer"&gt;dynamic staging setup&lt;/a&gt; allows us to get new features in front of the &lt;a href="https://www.aha.io/roadmapping/guide/product-management/work-with-engineers" rel="noopener noreferrer"&gt;product&lt;/a&gt;, marketing, and Customer Success teams early in the development process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All engineers should attempt to create a performant application. Our focus on monitoring and observability allows us to find problems in production and start the troubleshooting process. Some features may need to go back into development for optimization, but we can often solve the problem through algorithmic tweaks, indexing changes, or other &lt;a href="https://www.aha.io/engineering/articles/optimizing-with-the-postgresql-deterministic-query-planner" rel="noopener noreferrer"&gt;database management techniques&lt;/a&gt;. Our experience looking into these issues gives us a sense of where additional caching, reducing object allocations, or other techniques could be used to bolster end-user performance.&lt;/p&gt;

&lt;p&gt;These goals and techniques are also shared by the &lt;a href="https://www.atlassian.com/devops/what-is-devops/history-of-devops#:~:text=devops%20movement" rel="noopener noreferrer"&gt;DevOps movement&lt;/a&gt;, which attempts to push all the operations work back onto the engineering team. However, developing a &lt;a href="https://www.aha.io/engineering/articles/from-one-many-building-a-product-suite-with-a-monolith" rel="noopener noreferrer"&gt;large monolithic Rails application&lt;/a&gt; with a robust and modern front-end framework encompasses two very deep skill sets. Asking all of our engineers to also be AWS, networking, and database engineers is asking far too much.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our platform team serves and empowers the rest of the engineering organization. All of our goals unite to serve the company vision.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Making complicated problems simple&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without new features to uncover what is currently possible with our infrastructure, we wouldn't be able to solve complex problems. Part of that empowerment entails building out all-new infrastructure and paradigms that can be used to solve tomorrow's problems. We've made a massive effort toward integrating &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt; into our infrastructure this year, which is typical of this kind of investment. By having some space away from product-facing work, we're able to think of our product engineers as internal customers. We can build out tools and infrastructure that will support the development of future features and products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform for profit
&lt;/h2&gt;

&lt;p&gt;Remember the profit-center vs. cost-center distinction? New development generally gets billed as profit-generating, but the operations side of an organization has the unique ability to drive costs down. This is done through fine-tuning the spend on Cloud resources, optimizing execution to require less of those resources in the first place, and selecting time-saving technologies.&lt;/p&gt;

&lt;p&gt;Infrastructure budget is a vital component of the &lt;a href="https://www.aha.io/roadmapping/guide/roadmap/technology-roadmap" rel="noopener noreferrer"&gt;technical roadmap&lt;/a&gt; for any platform team. When the development team is bottlenecked for infrastructure reasons, the organization loses out on critical first-mover advantages in the marketplace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Poor engineering practices can lead to a deficit of our most important asset — the team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We want our teammates to love what they do — &lt;a href="https://www.aha.io/company/careers" rel="noopener noreferrer"&gt;increasing employee joy&lt;/a&gt; is the aim and core of our company's existence. And it makes financial sense too. Replacing an engineer's institutional knowledge and expertise takes time. Attrition makes your hiring plan doubly difficult to achieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need for speed
&lt;/h2&gt;

&lt;p&gt;Focusing on optimization allows platform engineers to improve the company's service in noticeable ways. Speed is a feature because performance expectations are similar to the delays we expect in a normal conversation. A response of 100ms feels instantaneous but delays longer than one second start to interrupt the user's flow of thought. Beyond four seconds, the user can no longer smoothly interact with a system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Products in your daily workflow should be reliable and resilient — these two factors drive the happy use of software.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Failures can occur through human error, hardware malfunction, or &lt;a href="https://www.aha.io/roadmapping/guide/agile/what-is-issue-tracking" rel="noopener noreferrer"&gt;bugs&lt;/a&gt; in vendor packages. However, my experience has shown that a system that recovers quickly when it fails is viewed more favorably than one that fails infrequently but has a longer recovery time.&lt;/p&gt;

&lt;p&gt;A system in this case may refer not only to your software and hardware, but also your operations, engineers, and customer success teams. When failures are still within the realm of exceptional behavior and are fixed rapidly, we find those customer interactions are generally positive — even &lt;a href="https://www.aha.io/lovability" rel="noopener noreferrer"&gt;lovable&lt;/a&gt;. Customers who love your product will keep using it (reducing churn, increasing profit) and expand the use of the product within their own organization. Platform teams support the broader engineering organization by being prepared to respond quickly to the most debilitating failure modes. This is a core overlap with SRE teams as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring engineering teams
&lt;/h2&gt;

&lt;p&gt;If SRE teams are the defense and engineers are the offense, then platform teams are the mid-fielders. They save goals as often as they score them. Their presence makes the rest of the engineering organization more effective at their core objectives.&lt;/p&gt;

&lt;p&gt;However, keep in mind the time spent toiling. If the platform, infrastructure, QA, and tooling engineers spend over half of their time responding directly to incidents or on-call work, you may need a dedicated reliability team.&lt;/p&gt;

&lt;p&gt;If every week has a new emergent behavior that requires platform engineers to drop everything and fix it, it's time to adopt more proactive measures to build resilience, monitoring, and fault-tolerance. And when those tasks take up more than half the platform team's time, it's time to honor reality and break off a dedicated reliability engineering team.&lt;/p&gt;

&lt;p&gt;Platform and SRE teams do much of the same work — the division between them is more like a gradient than a clear line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F270964cec5e0150f8845216b5c243786%2Fdevops.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aha.io%2F270964cec5e0150f8845216b5c243786%2Fdevops.jpeg" alt="devops"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the platform team is fully aligned with engineering and company goals, it can incorporate a healthy SRE culture that meets its needs. Focusing on observability, recovery, and reliability saves costs and increases reliability while the platform team works on enhancing the developer experience and building new infrastructure for future features or projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grow your career and be happy
&lt;/h3&gt;

&lt;p&gt;If this kind of alignment and environment sounds intriguing to you, come join us and &lt;a href="https://www.aha.io/company/careers/current-openings/#:~:text=engineering" rel="noopener noreferrer"&gt;work with the sharpest engineers&lt;/a&gt; I've ever had the pleasure of calling colleagues. Career happiness comes from doing meaningful work with motivated teammates and being appreciated for it. That is Aha! — a talented group of people changing how the best-known companies innovate and bring products to market.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>devops</category>
      <category>platform</category>
      <category>sre</category>
    </item>
    <item>
      <title>Technical debt isn't technically debt</title>
      <dc:creator>Jeremy Wells</dc:creator>
      <pubDate>Wed, 07 Sep 2022 23:40:58 +0000</pubDate>
      <link>https://dev.to/aha/technical-debt-isnt-technically-debt-4oke</link>
      <guid>https://dev.to/aha/technical-debt-isnt-technically-debt-4oke</guid>
      <description>&lt;p&gt;The term "&lt;a href="https://www.aha.io/roadmapping/guide/it-strategy/what-is-technical-debt"&gt;technical debt&lt;/a&gt;" has entered the standard lexicon of programming and software project development and has often been called out for being an incorrect metaphor. Yet it persists as a way to talk about the decisions made during software engineering. I believe it is over-used and often makes &lt;a href="https://www.atlassian.com/agile/software-development/technical-debt"&gt;technical conversations more confusing&lt;/a&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our team at Aha! has meaningful conversations about the trade-offs of software development without using the term "technical debt."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two common ways the term "technical debt" is used now. The first is when engineers are talking with someone who doesn't have the same technical context of a project. In this case, it may be used to smooth over the difficulty of communicating various ideas. Those ideas are typically: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can deliver feature 1 faster but that means feature 2 will take longer.&lt;/li&gt;
&lt;li&gt;This feature is going to take longer because of how we approached earlier features. You can see that those are conceptually similar other than the timing. The message is that the developer needs more time to work on these features. Calling it technical debt avoids the need to explain the finer details. You might also say:&lt;/li&gt;
&lt;li&gt;We’ve incurred technical debt so we need to clean that up before planning the next features.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This communicates that engineering is not happy with the code as it stands. We would like to work on it without needing to worry about delivering functional changes. The term is again used to avoid further explanation and scrutiny. It has entered the mindset of many people in software development that "technical debt" is something that just happens and needs repaying.&lt;/p&gt;

&lt;p&gt;The other common usage is between engineers as justification for making certain decisions or for why the code is the way it is. Many times it sounds like an excuse. If an engineer is embarrassed about the state of the code or architecture, they may call it technical debt to signal to other engineers that the problems arose due to external factors.&lt;/p&gt;

&lt;p&gt;I’ll address the problems with these usages, but first let’s consider what technical debt should actually mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is technical debt
&lt;/h2&gt;

&lt;p&gt;Let's remove all the built-up subjective meaning that has crept into the term "technical debt." Similar to monetary debt, it is a balance between having what you need now and the total costs plus risk. That balance plays on our tendency to want results right now and to play down the long-term consequences. Consider the technical debt thought process:&lt;/p&gt;

&lt;p&gt;You need to deliver a feature.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Option A: Deliver the current feature as fast as possible. Your subsequent features &lt;em&gt;may&lt;/em&gt; take longer due to more necessary patchwork.&lt;/li&gt;
&lt;li&gt;Option B: Take time to build a maintainable solution for the current feature. Your subsequent features &lt;em&gt;may&lt;/em&gt; require less heavy lifting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which do you choose? &lt;/p&gt;

&lt;h2&gt;
  
  
  Not technically debt
&lt;/h2&gt;

&lt;p&gt;I believe "debt" is simply the wrong way to describe this process. With monetary debt, your choice is clear and defensible: I need this money right now or I don’t. Ideally, the loan terms and outcomes are presented upfront.&lt;/p&gt;

&lt;p&gt;This technical choice is not so cut and dry. Is Option B better? How can we be sure that investing extra time now will pay off later? For financial assets, calculating risk is a well-established process. Software development isn't as predictable. Future product and business decisions may alter your plans altogether. A shifting roadmap may waste your big investment.&lt;/p&gt;

&lt;p&gt;Feasibility also plays a part. Software is built in layers — some of which we don't control. Architectural concepts emerge over time and change to meet the needs of the system. Is this choice apparent and real? Is the engineer working on the problem in a position to choose? I’ve been down enough dead-ends and thought experiments to say that often an apparent choice is a false one. Those experiments might inform later changes but that does not make the current implementation a debt. &lt;/p&gt;

&lt;h2&gt;
  
  
  Managing uncertainty
&lt;/h2&gt;

&lt;p&gt;Option A may slow down your subsequent features, but it may not. You may have truly found a faster solution. Option B may save you hassle in the long run, or it may not. Your time spent upfront may be wasted if the roadmap changes drastically. &lt;/p&gt;

&lt;p&gt;With all of the issues around estimating software projects, it is impossible to predict with certainty. The next feature might not be developed, the requirements might change. Traditional debt has fewer surprises. You don’t take out a loan on a car, discover it was a boat and find out you actually need to take it skiing.&lt;/p&gt;

&lt;p&gt;Consider this choice instead:&lt;/p&gt;

&lt;p&gt;You have one feature that you will deliver first and one follow-up feature.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Option A: Deliver the first feature faster with non-ideal code or technical choices to make the second feature easier.&lt;/li&gt;
&lt;li&gt;Option B: Take longer to deliver the first feature and perform less rework for the second.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With either option, you haven't created a debt. You've simply made decisions about the timeline. This is called planning and all good plans take some finessing.&lt;/p&gt;

&lt;p&gt;When relaying updates to cross-functional teammates, simply explain the problems and give realistic timelines based on what needs to happen. Blaming a delay on technical debt only hinders transparency and keeps your teammates in the dark.&lt;/p&gt;

&lt;p&gt;With your development team, technical debt should not be an excuse to hide behind. Decisions are made, mistakes happen, external forces are at work. Discuss what happened and determine your next steps. Your project cannot be bankrupt by technical debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investing time
&lt;/h2&gt;

&lt;p&gt;Rarely do we have all the time we need to make our code perfect. But that's okay. You may have heard that "Premature optimization is the root of all evil in programming," from &lt;a href="https://www-cs-faculty.stanford.edu/~knuth/taocp.html"&gt;Donald E Knuth&lt;/a&gt;. Certain ideas about code and software are worth tempering — such as making code flexible or thinking about inflection points. But too much can lead to premature optimizations, over-abstractions, and needless configuration. The code itself is easy to change. If you don’t need a reusable abstraction now, add one later. &lt;/p&gt;

&lt;p&gt;You've probably heard the saying, "Don't Repeat Yourself (DRY)" — and how applying that too early or aggressively can also introduce friction. Remember these rules of two and three. &lt;/p&gt;

&lt;p&gt;For small code, use the rule of three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I’m implementing this for the third time — I should introduce a generic way to handle this pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why three? Because you copied and pasted the first solution and made changes. On the third go, you understand the changes and are ready to solve it differently.&lt;/p&gt;

&lt;p&gt;For larger concepts, use the rule of two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I made this model and it worked well, but now I want to use it again and it doesn’t quite fit. I should restructure this to be useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll want to reuse the concepts that drive other parts of the codebase. They might not be in a good state for reuse, so now is the time to analyze and create layers and abstractions.&lt;/p&gt;

&lt;p&gt;The fact that you didn’t do that before is not technical debt. This is software development.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We work fast at Aha! and we use our products to build our products. Our team is happy, productive, and hiring — &lt;a href="https://www.aha.io/company/careers"&gt;join us!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technicaldebt</category>
    </item>
  </channel>
</rss>
