<?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: Tomer Brisker</title>
    <description>The latest articles on DEV Community by Tomer Brisker (@tbrisker).</description>
    <link>https://dev.to/tbrisker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F290263%2F6920fb63-0ad7-4677-a27e-f2a901b4c27a.jpeg</url>
      <title>DEV Community: Tomer Brisker</title>
      <link>https://dev.to/tbrisker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tbrisker"/>
    <language>en</language>
    <item>
      <title>Decompose Conditionals and its tradeoffs</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Sun, 11 Dec 2022 12:51:03 +0000</pubDate>
      <link>https://dev.to/tbrisker/decompose-conditionals-and-its-tradeoffs-36ba</link>
      <guid>https://dev.to/tbrisker/decompose-conditionals-and-its-tradeoffs-36ba</guid>
      <description>&lt;p&gt;Decomposing conditionals is a common refactoring strategy.&lt;br&gt;
The main idea is that a complex &lt;code&gt;if... else...&lt;/code&gt; statement is difficult to follow and understand, and can be decomposed into smaller pieces. But it does not come without a cost.&lt;/p&gt;




&lt;p&gt;Instead of having multi-line conditions, the decompose conditional pattern states that conditional logic should be extracted to 3 separate methods, one for each part of the statement: one for the condition itself, one for the "true" branch and one for the "false" branch.&lt;/p&gt;

&lt;p&gt;For example, if your code looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAwake&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;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Crying"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeSinceLastMeal&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&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;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diaper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDirty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeDiaper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;bottle&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewBabyBottle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FillWater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMilkFormula&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shake&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Burp&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="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cuddle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Decomposing the conditional would mean refactoring your code to look something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsHungry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeAndFeed&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="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baby&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Baby&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;IsHungry&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="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAwake&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;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Crying"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeSinceLastMeal&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baby&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Baby&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ChangeAndFeed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diaper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDirty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChangeDiaper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="n"&gt;bottle&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewBabyBottle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FillWater&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMilkFormula&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shake&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Burp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baby&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Baby&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FunTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cuddle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;baby&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Play&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;This change makes the main method flow much simpler to understand and follow, by abstracting away the complex logic that might not always be relevant for the reader. &lt;/p&gt;

&lt;p&gt;Extracting the logic to separate methods also allows us to more easily reuse or refactor our code in the future, as the logic is better encapsulated. For example, once our baby starts eating solid foods, we can modify the &lt;code&gt;ChangeAndFeed&lt;/code&gt; method to reflect that easily, without needing to touch any code that is not directly related to this change, and the same is true if the logic for checking if the baby is hungry ever changes - for example, once our baby starts eating at 4 hour intervals.&lt;/p&gt;

&lt;p&gt;Another benefit from decomposing the conditional is that we can now write unit tests for the decomposed methods, ensuring that our logic is correct, and that it will not break due to future changes.&lt;/p&gt;




&lt;p&gt;If decomposing conditionals is so great, than why don't we always do it? &lt;br&gt;
Like everything in software engineering, there is a tradeoff. &lt;/p&gt;

&lt;p&gt;When extracting logic to a separate method, there is additional overhead. While the overhead of a function call in most languages is minimal (except in very extreme cases, such as deep recursion or tightly resource-constrained environments), the overhead for the code reader is not. Software engineers spend much more time reading code than they do writing code, and we should always think about how we can make our code more readable. Jumping back and forth between method definitions and calls adds complexity for the reader who is interested in understanding what the code does, for example when reviewing or debugging it.&lt;/p&gt;

&lt;p&gt;Take the following code for example. Which version is easier to understand and follow, this one:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orgIDs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxItems&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userIDs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxItems&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;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too Many Items"&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;or this one?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;validateIDsSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orgIDs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userIDs&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="n"&gt;tooManyItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;....&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;validateIDsSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idGroup&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxItems&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&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="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;tooManyItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&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;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Too Many Items"&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;The keyword here is &lt;em&gt;Complexity&lt;/em&gt;. The question we should be asking is: "Is this piece of code complex enough that extracting it to a separate method will make it more readable or not?"&lt;br&gt;
If the answer is that the code is clear enough to understand inline, we should avoid decomposing it to a separate function unless there is a very good reason to do so - such as a need for re-usability or testing. &lt;/p&gt;




&lt;p&gt;The second example also shows another common risk we face when extracting logic to separate methods: a premature attempt at generalization. While the first version only checks two specific values, the second one tries to create a generic function that can accept any number of values. &lt;/p&gt;

&lt;p&gt;The generic version of the check creates additional complexity to handle all possible cases, instead of a simpler check to handle the specific case we are facing. &lt;br&gt;
If we have many similar cases with different values being checked, it could make sense to create a generic function that handles all cases. &lt;br&gt;
However, we often find a generic function being only used in one or two places, in which case - the added complexity usually doesn't pay off when compared to a non-generic version.&lt;/p&gt;

&lt;p&gt;We also don't yet know that all potential future cases will behave the same. For example, what if in the future different values will have different limits? The generic implementation assumes all values are limited by &lt;code&gt;maxItems&lt;/code&gt; - meaning if this assumption ever changes, this method will become even more complex. A non-generic implementation is easier to change as we discover new requirements.&lt;/p&gt;




&lt;p&gt;When used correctly, decomposing conditionals is a useful refactoring method that can make our code easier to understand, easier to test, and more reusable. However, before we approach refactoring our code, we must always keep in mind our end goals and consider the possible tradeoffs in each approach. &lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>cleancode</category>
      <category>go</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Attending recruiting fairs as an engineer</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Sun, 19 Jun 2022 18:49:20 +0000</pubDate>
      <link>https://dev.to/tbrisker/attending-recruiting-fairs-as-an-engineer-b89</link>
      <guid>https://dev.to/tbrisker/attending-recruiting-fairs-as-an-engineer-b89</guid>
      <description>&lt;p&gt;Over the years, I’ve attended many student recruitment fairs representing Red Hat, including a couple in the recent weeks. I always try to go to at least one fair every year if possible.&lt;/p&gt;

&lt;p&gt;But you might be wondering, is this the best usage of an engineer’s expensive work hours? Isn’t that the job of the talent acquisition team? &lt;br&gt;
In my opinion, there is a lot of value for engineers to take part in these events, both for the organization and for the engineers themselves. &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%2F0qx6k0y7hgc3cgaitmze.jpg" 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%2F0qx6k0y7hgc3cgaitmze.jpg" alt="Image description" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the organization's perspective, let’s look at the several different goals from attending these events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The obvious goal, &lt;strong&gt;recruiting new employees&lt;/strong&gt;. While recruiting is the TA team’s speciality, engineers can provide additional value - for example, answering candidates’ technological questions better, or quickly identifying promising candidates that might get missed otherwise. Candidates often prefer to talk with actual engineers and trust them more than the people whose job is to promote the company to them but lack in technical skills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Employee branding&lt;/strong&gt;: A few lucky students might get hired - but the impact of the fair can be much wider. Having enthusiastic engineers take part in the fair can do a lot in terms of exciting students about the company and how cool it would be to work there. Some students might not yet fit the requirements of your open positions, and an engineer can give them pointers and advice on what they need to do to get accepted in the future. The students are usually at the start of their career, and if you manage to make a good impression on them they might consider your company a good option for their entire career. Building the company’s reputation as a good employer helps get more candidates into the hiring pipeline. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Potential future customers&lt;/strong&gt;: If your company’s products or services are targeted at a technically-proficient user base, you might meet people who will go on to work at one of your future customers. Building awareness of your products could help with sales down the line, and who better to excite future engineers about how great your products are than the people working on them?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connecting employees to company mission&lt;/strong&gt;: When people promote the company, they need to learn more about what it does and how to explain its mission quickly to others. Representing the company is a great way to increase employee engagement with your company, and increase their awareness of the company strategy beyond their day-to-day work.&lt;/li&gt;
&lt;/ul&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%2F009po3nvjqn827hgu09a.jpg" 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%2F009po3nvjqn827hgu09a.jpg" alt="Image description" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But why should you, as an engineer, take precious time away from your development work to go to a student fair?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Company-wide impact&lt;/strong&gt;: One of the key criteria for career advancement in most companies is closely related to your impact. Affecting the hiring process is one of the most significant and long-lasting impacts you can make. The people hired today will be a key factor in the company’s future success, and they may even outlast you as an employee. If you manage to improve the quality of the candidates hired, this will have a compound impact for years to come.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice public speaking and interpersonal communication skills&lt;/strong&gt;: If you’re an introvert like me, talking to strangers can be very tough. Recruitment fairs provide you with an opportunity to talk to people who you know are interested in what you have to say and hearing about your job. Sometimes you will get a somewhat pre-scripted pitch, so you don’t even have to prepare too much content - only describe the company to candidates and talk with them about your job. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day out&lt;/strong&gt;: Whether you normally work from home or the office, recruitment fairs are a great chance to break out from the regular routine, get a bit of vitamin D into your blood, stretch your muscles and breathe some fresh air. Your body will thank you for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking&lt;/strong&gt;: Building a strong and wide network is key to growing your career and landing future opportunities. You will likely find yourself at the booth with people from your organization who you might not have worked with before. You might even make some connections with candidates or people staffing other companies’ booths. Take the time to get to know some of them better - who knows where they’ll be in the future and how they might be able to help you.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hiring</category>
      <category>recruiting</category>
      <category>career</category>
      <category>culture</category>
    </item>
    <item>
      <title>When an Array is not an Array</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Mon, 27 Aug 2018 10:32:44 +0000</pubDate>
      <link>https://dev.to/tbrisker/when-an-array-is-not-an-array-49hg</link>
      <guid>https://dev.to/tbrisker/when-an-array-is-not-an-array-49hg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The story of a simple array sort gone wrong&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part of the release process of &lt;a href="https://www.theforeman.org" rel="noopener noreferrer"&gt;the Foreman&lt;/a&gt; project includes extracting all strings from the source code for translation, and pulling updated translations from an online service we use called &lt;a href="https://www.transifex.com/" rel="noopener noreferrer"&gt;Transifex&lt;/a&gt;. As part of the release process for version 1.19.0, I ran the rake task which handles this step.&lt;/p&gt;

&lt;p&gt;Unfortunately, it failed with a strange error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ArgumentError: comparison of Array with Array failed
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/po.rb:270:in `sort_by’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/po.rb:270:in `sort_by_msgid’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/po.rb:227:in `sort’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/po.rb:208:in `to_s’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/tools/msgcat.rb:57:in `run’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/tools/msgcat.rb:32:in `run’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext-3.2.9/lib/gettext/tools/task.rb:391:in `block in define_po_file_task’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/gettext_i18n_rails-1.8.0/lib/gettext_i18n_rails/tasks.rb:65:in `block (2 levels) in &amp;lt;top (required)&amp;gt;’
/home/tbrisker/.rvm/gems/ruby-2.5.1@foreman/gems/rake-12.3.1/exe/rake:27:in `&amp;lt;top (required)&amp;gt;’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, let’s look at the failing function’s code:&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;sort_by_msgid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&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;msgid_entry&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# msgid_entry = [[msgctxt, msgid], POEntry]&lt;/span&gt;
    &lt;span class="n"&gt;msgid_entry&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="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;Hmm… That’s quite odd, looks like a fairly simple sorting function. Looking at the gem’s git &lt;a href="https://github.com/ruby-gettext/gettext/blame/master/lib/gettext/po.rb#L268" rel="noopener noreferrer"&gt;history&lt;/a&gt; indicates that this function hasn’t changed in ages — so why is it failing now? The comment seems pretty cryptic, and without digging too much into the gem’s internals it would be difficult to understand what it means. Time to pull out the ol’ debugger and stick a &lt;code&gt;binding.pry&lt;/code&gt; breakpoint inside the function to try and understand what is going on here.&lt;/p&gt;

&lt;p&gt;This is what one of the msgid_entry objects looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[nil, "Failed to fetch: "],
 #&amp;lt;GetText::POEntry:0x00000000144a3a80
  @extracted_comment="",
  @flags=[],
  @msgctxt=nil,
  @msgid="Failed to fetch: ",
  @msgid_plural=nil,
  @msgstr="Falha ao buscar:",
  @param_number=0,
  @param_type=[:msgid, :separator, :msgstr],
  @previous="",
  @references=["../app/assets/javascripts/application.js:74"],
  @translator_comment="",
  @type=:normal&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is an array that is composed of two elements — the first, an array that has two elements itself, and the second is a &lt;code&gt;GetText::POEntry&lt;/code&gt; object, that is some sort of internal gettext object representing the translation. Now the comment in the code makes a bit more sense — the array contains a message context (in this case, &lt;code&gt;nil&lt;/code&gt;) and a message ID, in this case, the string &lt;code&gt;"Failed to fetch: "&lt;/code&gt;. The sorting function sorts the entries by the values of this array (&lt;code&gt;msgid_entry[0]&lt;/code&gt;). So for some reason, one (or more) of the entries contains a problematic value in this array.&lt;/p&gt;

&lt;p&gt;Stepping a few iterations in the loop didn’t cause the exception to be raised, and considering there are over 3,000 entries in the list (presumably, one for every string that needs translation), manually finding the problematic entry can be painstaking.&lt;/p&gt;

&lt;p&gt;After a while of playing around with conditional breakpoints, the culprit was found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[["(%s host)", "(%s hosts)"],
 #&amp;lt;GetText::POEntry:0x000000000f1ea780
  @extracted_comment="",
  @flags=[],
  @msgctxt="(%s host)",
  @msgid="(%s hosts)",
  @msgid_plural=nil,
  @msgstr=nil,
  @param_number=0,
  @param_type=[:msgctxt, :msgid, :msgstr],
  @previous="",
  @references=["../webpack/assets/javascripts/react_app/components/factCharts/index.js:86"],
  @translator_comment="",
  @type=:msgctxt&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was the only entry that had something other than &lt;code&gt;nil&lt;/code&gt; for the first element of the first array — the message context. To make my work easier, the second element included the reference to the exact line of code where the string came from.&lt;/p&gt;

&lt;p&gt;Looking at the code, the &lt;a href="https://github.com/theforeman/foreman/pull/5860/files#diff-e44063a1ea27ac907ae659670db4eb54R86" rel="noopener noreferrer"&gt;fix&lt;/a&gt; was clear — someone had a typo and used the incorrect method for marking the string for translation. So instead of using the method needed to mark the string as one that requires both singular and plural translation, the regular translation method was called, which apparently accepts an additional parameter for the context.&lt;/p&gt;

&lt;p&gt;With the issue found and fixed, one question remained — why did this fail with the cryptic &lt;code&gt;comparison of Array with Array failed&lt;/code&gt; error message?&lt;/p&gt;

&lt;p&gt;To understand this, we need to dive a bit into how Ruby’s &lt;code&gt;sort_by&lt;/code&gt; function works. Simply put, it accepts a block that gets as its argument the elements in the sorted object, and sorts them by the return value of the block. In our case, the entries are sorted by the value of the first element in each entry — the array containing the message context and ID.&lt;/p&gt;

&lt;p&gt;But how does Ruby sort arrays? Turns out it sorts them by comparing one element at a time. To compare the elements, it calls the function &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt;, which returns -1, 0, or 1 that indicate if the first element is smaller, equal or larger than the other. However, it can also return &lt;code&gt;nil&lt;/code&gt; if the two elements can’t be compared, which is exactly what is happening here.&lt;/p&gt;

&lt;p&gt;To illustrate, let’s look at what happens when we compare two strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1] pry(main)&amp;gt; 'a' &amp;lt;=&amp;gt; 'foo'
=&amp;gt; -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;a&lt;/code&gt; is lexically smaller than &lt;code&gt;foo&lt;/code&gt;, the function returns -1. But what happens when we try to compare a string with &lt;code&gt;nil&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2] pry(main)&amp;gt; nil &amp;lt;=&amp;gt; 'foo'
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, since Ruby doesn’t know how to compare the two different object types, it returns &lt;code&gt;nil&lt;/code&gt;. But what does that have to do with our error? Well, if you recall, Ruby &lt;a href="https://docs.ruby-lang.org/en/2.5.0/Array.html#method-i-3C-3D-3E" rel="noopener noreferrer"&gt;compares arrays&lt;/a&gt; by iterating over the array elements. Now lets see what happens if it tries to compare arrays containing elements that can’t be compared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[3] pry(main)&amp;gt; [[nil, 'foo'], ['bar', 'baz']].sort
ArgumentError: comparison of Array with Array failed
from (pry):4:in `sort'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the arrays being sorted by has a string for its first element, while the other has &lt;code&gt;nil&lt;/code&gt;. This means Ruby doesn’t know how to sort these arrays, leading to an error. Unsurprisingly, since this is exactly the case as we were hitting with our translations, with one array containing a string where all others had &lt;code&gt;nil&lt;/code&gt;, the same error was raised as when we tried the sorting in this example.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>debugging</category>
      <category>ruby</category>
    </item>
    <item>
      <title>It works on my machine</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Sun, 08 Jul 2018 12:45:45 +0000</pubDate>
      <link>https://dev.to/tbrisker/it-works-on-my-machine-1h5n</link>
      <guid>https://dev.to/tbrisker/it-works-on-my-machine-1h5n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;When Webpack is broken, but only in the packaged build — or how I created my first Webpack plugin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First, this story requires some background and a basic understanding of the architecture of the main project I’m working on, &lt;a href="https://theforeman.org" rel="noopener noreferrer"&gt;Foreman&lt;/a&gt;. Foreman is a Ruby on Rails application, used for infrastructure management. It has many plugins that add various functionality, which are written as Rails Engines. Since this application is used to manage critical infrastructure, it is installed on-premise and is shipped as packages — both .deb and .rpm. Many of the various plugins are also packaged and shipped as .rpm and .deb files.&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%2Fi%2Fmwug4qbj9zva8wyh3lw6.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmwug4qbj9zva8wyh3lw6.png" alt="Foreman" width="455" height="190"&gt;&lt;/a&gt;The Foreman Project Logo&lt;/p&gt;

&lt;p&gt;About 2 years ago we started modernizing our application’s front-end. To achieve this, it meant integrating Webpack and npm into our assets build process. This allowed us to use cool new technologies such as React and ES6 in our client side code, as well as start to solve long standing issues we had with our legacy JS code — such as lack of linting and unit-tests. The initial step of creating a Webpack build process took a while. One of the reasons is that RPM package builders must run in a disconnected environment, so we had to figure a way of providing all the node modules to the builder as RPMs at build time, but that is a story for an entire post on its own. Once that was solved, we started gradually migrating or replacing the legacy code-base with new, Webpack-managed code.&lt;/p&gt;

&lt;p&gt;A major challenge we had recently was providing plugins with a way to leverage the existing npm and Webpack stack in Foreman core for writing their own client-side code. We didn’t want to force every plugin to create its own build pipeline, and we didn’t want to have multiple copies of every library included in each plugin’s JS bundle. This was solved with some clever engineering work by some of my colleagues. The solution involved compiling the Webpack assets for both plugins and the core application from the main application’s code base, while providing plugins with a &lt;code&gt;vendor.js&lt;/code&gt; file that contains multiple shared libraries — such as React, Redux, Lodash and more. This worked perfectly fine during development, and even when compiling assets for production locally.&lt;/p&gt;

&lt;p&gt;However, when we tried installing the nightly packages with plugins, something strange started happening. The core pages which included Webpack-managed assets worked fine, but the plugin pages requiring use of the plugin’s Webpack assets didn’t load. Looking in the console, it didn’t look promising:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

TypeError: e[t] is undefined


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

&lt;/div&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%2Fi%2Fp5uggoh45iak314uammk.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%2Fi%2Fp5uggoh45iak314uammk.jpeg" alt="E.T." width="620" height="400"&gt;&lt;/a&gt;e[t]&lt;/p&gt;

&lt;p&gt;Now, part of the Webpack build process includes minifying the js code, making it smaller to download but much more difficult to debug. Luckily the browser developer tools knows how to prettify the code, so it is at least semi-readable. This pointed to the error being in the following function, on line 9:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Well, that’s not much better… but at least .exports hints at what this does. Comparing to the unminified and looking at the code around this function led me to understand that this is part of the Webpack code that handles module exports and makes them available to other modules, such as the plugin. But what is &lt;code&gt;e[t]&lt;/code&gt;? And why is it failing here?&lt;/p&gt;

&lt;p&gt;Since I know the failure occurs when &lt;code&gt;e[t]===undefined&lt;/code&gt;, I set a conditional breakpoint on this line in my browser’s developer tools and reloaded the page. Now I could finally see what is happening here. &lt;code&gt;e&lt;/code&gt; seems to be an object mapping hashes to functions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

» e
{0: ƒ, 00bb67fca9d5ae35c4aa: ƒ, 0139edb80f5e8e5773b7: ƒ, 01637e90596e3c444fa8: ƒ, 01d23de99989e6fe314f: ƒ, 01f59de879b1bcfbb8e7: ƒ, …}


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

&lt;/div&gt;
&lt;p&gt;And &lt;code&gt;t&lt;/code&gt; is a hash — &lt;code&gt;2278734bfcaa57409dda&lt;/code&gt; in this case. But for some reason, this specific hash isn’t included in the e object. Digging inside the plugin’s minified code, I searched for the hash to try and figure out what it meant. This allowed me to make a guess at which module this hash should map to, but surprisingly, it was a module that should have been included inside &lt;code&gt;vendor.js&lt;/code&gt;! So what happened here? Where did this hash come from? why was this working fine in the development environment? Looking in our Webpack config file, I found the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Git history indicated that &lt;code&gt;HashedModuleIdsPlugin&lt;/code&gt; was introduced &lt;a href="https://github.com/theforeman/foreman/pull/5579" rel="noopener noreferrer"&gt;recently&lt;/a&gt;, in an attempt to allow plugins to use modules by creating a stable identifier for each module. Apparently, the default Webpack configuration generates a sequential ID for each module, making it difficult to use from inside plugins as the module IDs would change every time a module was added or removed. This explained why the hashes were only present in production, but not why this worked when compiling the Webpack assets for production locally and failed when using the bundles produced by our RPM builders. I needed to understand how this hash was generated.&lt;/p&gt;

&lt;p&gt;Reading the Webpack documentation for this plugin, I found out that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This plugin will cause hashes to be &lt;strong&gt;based on the relative path of the module&lt;/strong&gt; , generating a four character string as the module id.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This led me to suspect that for some reason or other, when generating the Webpack bundles on the builders, the relative paths to the node modules were different for the plugins to those that were used when building Foreman core. However, from hashes this theory was very difficult to confirm. I needed some way to find the relative path that Webpack was using to generate the hash.&lt;/p&gt;

&lt;p&gt;After some research, I found there is a plugin that does just that — NamedModulesPlugin. For some reason, the documentation (that has been &lt;a href="https://github.com/webpack/webpack.js.org/pull/2287" rel="noopener noreferrer"&gt;deleted&lt;/a&gt; recently) claimed it should be used only in development and that it will show the relative path to the modules only when Hot Module Replacement is active. Looking at the code and trying it out, though, seemed to indicate that this is exactly what I needed to verify my hypothesis. When I replaced &lt;code&gt;HashedModuleIdsPlugin&lt;/code&gt; with &lt;code&gt;NamedModulesPlugin&lt;/code&gt;, the hashes were replaced with relative paths, both in the object mapping module identifiers to functions and in the code calling them. Locally, this continued working just as before with plugins, but the real test was when running with plugins built as RPMs.&lt;/p&gt;

&lt;p&gt;Once the change to &lt;code&gt;NamedModulesPlugin&lt;/code&gt; was &lt;a href="https://github.com/theforeman/foreman/pull/5723" rel="noopener noreferrer"&gt;merged&lt;/a&gt; into the project, it was time to rebuild the packages with the change and see if and what failed now. It took a couple of days to get the packages built again, since in the meantime some unrelated changes were merged that led to broken builds. But once they finally were, the moment of truth arrived — was my hypothesis correct?&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%2Fi%2Flbwtshyo8v8q0xmdqrgc.jpg" 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%2Fi%2Flbwtshyo8v8q0xmdqrgc.jpg" alt="This is the sign you've been looking for" width="800" height="532"&gt;&lt;/a&gt;“A bright neon on a brick wall in a store” by &lt;a href="https://unsplash.com/@austinchan?utm_source=medium&amp;amp;utm_medium=referral" rel="noopener noreferrer"&gt;Austin Chan&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unsurprisingly, the same &lt;code&gt;e[t]&lt;/code&gt; is undefined error showed up again. Only difference is that this time t should point to the actual module that was failing to load, so we can finally figure out why this was failing. Returning the conditional breakpoint to the vendor.js code allowed me to find what it was:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

» t
"../../../../../../../usr/lib/node_modules/react/index.js"


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

&lt;/div&gt;
&lt;p&gt;React? That’s odd! We use React in multiple pages in the core application as well, and they work just fine, so it must be available in &lt;code&gt;vendor.js&lt;/code&gt;! Quick &lt;code&gt;CTRL+f&lt;/code&gt; for &lt;code&gt;react&lt;/code&gt; in &lt;code&gt;vendor.js&lt;/code&gt; led to identifying the culprit. This is the identifier that was present in &lt;code&gt;vendor.js&lt;/code&gt; for the React module:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

"../../../../usr/lib/node_modules/react/index.js"


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

&lt;/div&gt;
&lt;p&gt;Bingo! Notice the different number of leading &lt;code&gt;../&lt;/code&gt;’s compared to the identifier the plugin was looking for. This indicated that indeed, during the build process, Webpack was being run from different paths when building the plugin, compared to when building Foreman core.&lt;/p&gt;

&lt;p&gt;Now that we finally got to the root of the issue, it was time to fix it. There were two different ways we could go about this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sure that the build always occurs from the same root directory, leading to consistent module IDs.&lt;/li&gt;
&lt;li&gt;Create a unique identifier for the modules that doesn’t care about where the build was started from, only about the module itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Option 1 could possibly work, but it required some deeper digging in our fairly complex build pipeline, followed by changes that may lead to other unexpected issues further down the process. It would also tie the whole build process to the Webpack configuration and reduce its flexibility in the future.&lt;/p&gt;

&lt;p&gt;Option 2 required finding a way to make sure that module IDs are consistent across builds, regardless of what folder the build process was started from. One idea that was brought up was to use a hash of the module’s entire source code, but that would mean that every time any module is changed, all plugins would have to be rebuilt — since the hash would change as well. While for some changes it makes sense to also have plugins updated, usually module APIs don’t change much between versions, so that a minor change shouldn’t matter to the consuming plugins.&lt;/p&gt;

&lt;p&gt;The solution that was finally decided on was to strip everything up to &lt;code&gt;node_modules&lt;/code&gt; from the path for the module — so that React, for example, would get an ID of &lt;code&gt;node_modules/react/index.js&lt;/code&gt; instead of something like &lt;code&gt;../../../../usr/lib/node_modules/react/index.js&lt;/code&gt;. We don’t really care where the &lt;code&gt;node_modules&lt;/code&gt; folder is, and it shouldn’t matter to plugins either. This ID will remain consistent across builds, and for as long as the module doesn’t change its internal structure. But how can we do this?&lt;/p&gt;

&lt;p&gt;To figure this out, I looked at the &lt;a href="https://github.com/webpack/webpack/blob/webpack-3/lib/NamedModulesPlugin.js" rel="noopener noreferrer"&gt;source code&lt;/a&gt; of the &lt;code&gt;NamedModulesPlugin&lt;/code&gt; that is shipped with Webpack 3 (the version we are currently using). The code was surprisingly short — a loop over all modules, setting the ID using &lt;code&gt;libIdent()&lt;/code&gt;. Some searching inside webpack code allowed me to understand the this function call will return the path to the module, without trying to dig to much into its internals.&lt;/p&gt;

&lt;p&gt;Since the plugin doesn’t provide any means of modifying the ID, what was needed now was to make a new Webpack plugin that strips everything up to &lt;code&gt;node_modules&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This code is almost the same as the original &lt;code&gt;NamedModulesPlugin&lt;/code&gt;. The only change I added are lines 14–16, which strip out the extra part of the path from the module ID where needed.&lt;/p&gt;

&lt;p&gt;As I assume others may run into this or similar issues in the future, I have extracted this plugin from our project to its own &lt;a href="https://github.com/tbrisker/simple-named-modules-plugin-webpack" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, and released it as a &lt;a href="https://www.npmjs.com/package/simple-named-modules-plugin-webpack" rel="noopener noreferrer"&gt;node module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While this story was mostly written in the first person, as it depicts my perspective of the events, it is important to note that this solution, like everything I work on, is the result of a team effort.&lt;/p&gt;

&lt;p&gt;Special thanks go to all those who helped in the process of fixing this issue, in no particular order: Daniel Lobato Garcia, Ewoud Kohl van Wijngaarden, Eric Helms, Avi Sharvit, Ohad Levy, Walden Raines, and anyone else who lent a hand and I forgot to mention.&lt;/p&gt;

&lt;p&gt;This solution would also not have been possible if Webpack hadn’t been an open-source project — as I would not have been able to dig into its internals, extract and modify code from it otherwise.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>javascript</category>
      <category>rails</category>
      <category>webpack</category>
    </item>
    <item>
      <title>Bug report to pull request in under 1 hour</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Mon, 12 Feb 2018 21:53:14 +0000</pubDate>
      <link>https://dev.to/tbrisker/bug-report-to-pull-request-in-under-1-hour-2d4e</link>
      <guid>https://dev.to/tbrisker/bug-report-to-pull-request-in-under-1-hour-2d4e</guid>
      <description>&lt;p&gt;A user came in to our IRC channel today complaining about an issue with searching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[16:31] &amp;lt;user&amp;gt; Why is it that when I search parent_hostgroup = “somerandom” in
the “Hosts” searchbox, where “somerandom” doesn’t match anything, it gives me a 
list of all hosts instead of none? I have some issues with this behavior when 
performing searches using the API
[16:40] &amp;lt;tbrisker&amp;gt; user: sounds like a bug, can you open an issue on 
projects.theforeman.org?
[16:49] &amp;lt;user&amp;gt; tbrisker, https://projects.theforeman.org/issues/22556
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Luckily, this one was a quick fix. But let’s dive in together and understand why it failed in the first place.&lt;/p&gt;

&lt;p&gt;We use a gem called &lt;a href="https://github.com/wvanbergen/scoped_search/"&gt;Scoped Search&lt;/a&gt; to handle searches in our application. It allows us to provide our users with the ability to search the various resources in the application using a powerful query language and auto-completer. While for most cases scoped search Just Works™, sometimes more complex logic is needed for certain queries. This is possible by telling scoped search to use an external method to generate the query by passing an :ext_method parameter to the search definition.&lt;/p&gt;

&lt;p&gt;The case of parent_hostgroup is one such case. A host in Foreman can belong to a host group, which in turn might be a child of one or more host groups. To allow searching by parent host group, i.e. any hosts that belong to a certain host group or any of its children, an external method is needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;search_by_hostgroup_and_descendants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sanitize_sql_for_conditions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"hostgroups.title &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value_to_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
  &lt;span class="c1"&gt;# Only one hostgroup (first) is used to determined descendants. Future TODO - alert if result results more than one hostgroup&lt;/span&gt;
  &lt;span class="n"&gt;hostgroup&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Hostgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unscoped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_taxonomy_scope&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="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="n"&gt;hostgroup_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hostgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subtree_ids&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hostgroup_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hosts.hostgroup_id IN (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;hostgroup_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hosts.id &amp;lt; 0"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:conditions&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Can you spot the bug?&lt;/p&gt;

&lt;p&gt;It’s on line 5. If no host groups match the search on line 4, &lt;code&gt;hostgroup&lt;/code&gt; will be &lt;code&gt;nil&lt;/code&gt;. That means that &lt;code&gt;hostgroup.subtree_ids&lt;/code&gt; should raise an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NoMethodError: undefined method `subtree_ids' for nil:NilClass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That will be easy to fix, we just need to make sure that the hostgroup is present before collecting its sub-tree ids:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;search_by_hostgroup_and_descendants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sanitize_sql_for_conditions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"hostgroups.title &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value_to_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
  &lt;span class="c1"&gt;# Only one hostgroup (first) is used to determined descendants. Future TODO - alert if result results more than one hostgroup&lt;/span&gt;
  &lt;span class="n"&gt;hostgroup&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Hostgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unscoped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_taxonomy_scope&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="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hostgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
    &lt;span class="n"&gt;hostgroup_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hostgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subtree_ids&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hosts.hostgroup_id IN (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;hostgroup_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1 &amp;lt; 0"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:conditions&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And sure enough, less then an hour after the initial report, I had a pull request ready, which also included a unit test to ensure this function won’t break again in the future:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[17:23] &amp;lt;tbrisker&amp;gt; user: https://github.com/theforeman/foreman/pull/5249
[17:24] &amp;lt;user&amp;gt; tbrisker, thanks! :)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;34 minutes of work and one happy user!&lt;/p&gt;

&lt;p&gt;Since I try to follow the &lt;a href="http://programmer.97things.oreilly.com/wiki/index.php/The_Boy_Scout_Rule"&gt;Boy Scout Rule&lt;/a&gt;, I also fixed a minor issue on line 9: the idea here is to return a condition that is always evaluated to false, thereby returning no results for the search. However, when the DB sees a condition like host.id &amp;lt; 0, it checks if any of the IDs are in fact smaller then 0. When you have tens of thousands of objects, this condition combined with other conditions (like default scope, default order, STI type etc.), even when executed against the index, can still take some time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;foreman&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="k"&gt;analyze&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="k"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="n"&gt;hosts&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt; 
&lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; 
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6134&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;702&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;702&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quicksort&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
 &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;hosts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6134&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;692&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;692&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&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;Recheck&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="k"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;Rows&lt;/span&gt; &lt;span class="n"&gt;Removed&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;43835&lt;/span&gt;
 &lt;span class="n"&gt;Heap&lt;/span&gt; &lt;span class="n"&gt;Blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1429&lt;/span&gt;
 &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Bitmap&lt;/span&gt; &lt;span class="k"&gt;Index&lt;/span&gt; &lt;span class="n"&gt;Scan&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;index_hosts_on_type&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&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;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&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="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;268&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;268&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;43835&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&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;Index&lt;/span&gt; &lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="k"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Managed&lt;/span&gt;&lt;span class="err"&gt;’&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;Planning&lt;/span&gt; &lt;span class="nb"&gt;time&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;359&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
 &lt;span class="n"&gt;Execution&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;808&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ok, that’s not too bad 😄&lt;/p&gt;

&lt;p&gt;But on the other hand, when the RDBMS sees a condition like 1 &amp;lt; 0 it immediately evaluates it to false and returns much faster (by about 3 orders of magnitude):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;foreman&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="k"&gt;explain&lt;/span&gt; &lt;span class="k"&gt;analyze&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"hosts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"hosts"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"hosts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"type"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Host::Managed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"hosts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;PLAN&lt;/span&gt; 
&lt;span class="c1"&gt;------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&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;01&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;02&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6134&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&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;011&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;011&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
 &lt;span class="n"&gt;Sort&lt;/span&gt; &lt;span class="k"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quicksort&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
 &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&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;00&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;00&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6134&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&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;001&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;001&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;One&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Time&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
 &lt;span class="n"&gt;Planning&lt;/span&gt; &lt;span class="nb"&gt;time&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;241&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
 &lt;span class="n"&gt;Execution&lt;/span&gt; &lt;span class="nb"&gt;time&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;077&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Not a huge gain, but doesn’t hurt either. Perhaps learning this trick may come in handy for other cases, where it may lead to a more significant gain.&lt;/p&gt;

&lt;p&gt;But wait, the user wasn’t actually complaining about an error. They complained that when the parent host group doesn’t exist, all hosts are returned instead of none of them. Why didn’t they get the &lt;code&gt;NoMethodError&lt;/code&gt;? That’s peculiar!&lt;/p&gt;

&lt;p&gt;Good thing the Scoped Search gem is Open Source, I can take a look inside and try to figure out what happened.&lt;/p&gt;

&lt;p&gt;Here is the code that handles the external method definitions inside Scoped Search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;to_ext_method_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;raise&lt;/span&gt; &lt;span class="no"&gt;ScopedSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryNotSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' doesn't respond to '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext_method&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="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ScopedSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryNotSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"external method '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' should return hash"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kind_of?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
  &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:include&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:include&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:joins&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:joins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:conditions&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:parameter&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&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;return&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Line 3 is the line that actually calls the external method when needed.&lt;/p&gt;

&lt;p&gt;But what is that &lt;code&gt;rescue {}&lt;/code&gt; doing there? That means that any error that occurs in the external method is swallowed silently, and the search will execute as if there was no condition at all! This is effectively suppressing any errors inside the external method, which is considered &lt;a href="https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions"&gt;bad practice&lt;/a&gt; in Ruby and in programming in general. There are two reasons this is considered bad practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user hitting an error (for example, by sending an invalid search query) will receive no indication that something went wrong. They may incorrectly assume the results they received were correct, which could lead them to take further actions that are mistaken. In our example, say a user wants to create a script that deletes all hosts in a certain host group and its children. If they mistype the host group name, instead of receiving an error or no results, they may end up deleting all of their hosts!&lt;/li&gt;
&lt;li&gt;The developer creating the method who’s exceptions are being suppressed may think it is functioning correctly, and receive no indication that there is any issue in it. In this case, looking at the git history, this bug has existed for 3.5 years without being noticed!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, thanks to the power of Open Source, I fixed the issue in Scoped Search and sent the maintainers a &lt;a href="https://github.com/wvanbergen/scoped_search/pull/173"&gt;Pull Request&lt;/a&gt; as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;to_ext_method_sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;raise&lt;/span&gt; &lt;span class="no"&gt;ScopedSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryNotSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' doesn't respond to '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext_method&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="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;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="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ScopedSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryNotSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"external method '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' failed with error: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ScopedSearch&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QueryNotSupported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"external method '&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;ext_method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' should return hash"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kind_of?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
  &lt;span class="n"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:include&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:include&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:joins&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:joins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:conditions&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:parameter&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&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;return&lt;/span&gt; &lt;span class="n"&gt;sql&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 let’s just hope they like it enough to merge it into the project! 😃&lt;/p&gt;

&lt;p&gt;Thank you for reading, I would be happy to hear feedback in the comments below or &lt;a href="https://twitter.com/tbrisker_pro"&gt;on twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>opensource</category>
      <category>rails</category>
    </item>
    <item>
      <title>Strong Parameters in Rails — down the rabbit hole</title>
      <dc:creator>Tomer Brisker</dc:creator>
      <pubDate>Wed, 17 Jan 2018 22:40:45 +0000</pubDate>
      <link>https://dev.to/tbrisker/strong-parameters-in-rails-down-the-rabbit-hole-32gk</link>
      <guid>https://dev.to/tbrisker/strong-parameters-in-rails-down-the-rabbit-hole-32gk</guid>
      <description>&lt;p&gt;It all started with a bug report (doesn’t it always?). A user was complaining that when they passed an incorrect argument to our API by mistake, they didn’t get any error message. Sounds like a quick fix, right?&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;Some background first, simplified greatly for the purpose of this post. Our application, like many others, has users. A user can belong to multiple organizations. Our REST API allows updating a user by PUTting a JSON object to the relevant endpoint, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"organization_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But our user had a typo. This is the JSON they sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"organization_ids"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It seemed to work fine — they got no error message. Perhaps it worked even too well: When they tried to pass a non-existing organization ID, it still returned a successful response! In fact, whenever they forgot to wrap the ID in an array, that parameter was simply ignored. No error response, no mention in the logs of something being wrong, nothing.&lt;/p&gt;

&lt;p&gt;After I reproduced the bug in my development environment, I started digging in. With some help from pry inside the controller, I saw that while the &lt;code&gt;organization_ids&lt;/code&gt; parameter was present in the request, it wasn’t there in the &lt;code&gt;user_params&lt;/code&gt; object that contains the allowed parameters for assignment. This pointed me in the direction of Strong Parameters.&lt;/p&gt;

&lt;p&gt;Strong Parameters is a feature of Rails that prevents assigning request parameters to objects unless they have been explicitly permitted. It has its own DSL (Domain Specific Language, or in other words, a predefined syntax it understands), that allows you to indicate what parameters should be allowed. It also lets you indicate if each parameter should be a hash, array or scalar (i.e. integer, string, etc.), as well as some other functionality that is not relevant for this post.&lt;/p&gt;

&lt;p&gt;Here is an excerpt from our definition of permitted parameters in the users controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit&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;:login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:mail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:organization_ids&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ok, so Strong Parameters expects an array for the :organization_ids parameter, but it gets an integer instead. So why am I not seeing any indication that it failed in the logs?&lt;/p&gt;

&lt;p&gt;Time to dig into the Rails code. Good thing it’s Open Source and I can just read it myself! Here is the &lt;a href="https://github.com/rails/rails/blob/v5.1.4/actionpack/lib/action_controller/metal/strong_parameters.rb#L522"&gt;function&lt;/a&gt; that does the actual filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;filters&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="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&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;filter&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;
      &lt;span class="n"&gt;permitted_scalar_filter&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;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt;
      &lt;span class="n"&gt;hash_filter&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;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;unpermitted_parameters!&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="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_on_unpermitted_parameters&lt;/span&gt;

  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So looks like it does have some sort of handling for the unpermitted parameters… Lets take another &lt;a href="https://github.com/rails/rails/blob/v5.1.4/actionpack/lib/action_controller/metal/strong_parameters.rb#L855"&gt;step&lt;/a&gt; inside the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;unpermitted_parameters!&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;unpermitted_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unpermitted_keys&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unpermitted_keys&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;case&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_on_unpermitted_parameters&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:log&lt;/span&gt;
      &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"unpermitted_parameters.action_controller"&lt;/span&gt;
      &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;keys: &lt;/span&gt;&lt;span class="n"&gt;unpermitted_keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:raise&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UnpermittedParameters&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;unpermitted_keys&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;But wait, what is this &lt;code&gt;action_on_unpermitted_parameters&lt;/code&gt; thing? Turns out this is a configuration option you can define in your application initialization. So I looked in our &lt;code&gt;application.rb&lt;/code&gt; and this is what we had defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_on_unpermitted_parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:strict&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Well, this explains why we saw nothing! This isn’t actually a value this option expects!&lt;/p&gt;

&lt;p&gt;Digging in git history, turns out the &lt;code&gt;:strict&lt;/code&gt; option is actually a left-over from Rails 3 days  —  when it was passed to &lt;code&gt;config.active_record.mass_assignment_sanitizer&lt;/code&gt;, which was a previous method of sanitizing input that accepted the &lt;code&gt;:strict&lt;/code&gt; option. When we implemented Strong Parameters in preparation for the Rails 5 upgrade, this option was renamed correctly, but its value remained the same. Which means that for about a year and half, we were silently dropping all of the filtered parameters instead of raising an exception.&lt;/p&gt;

&lt;p&gt;Great! Now all I have to do is replace &lt;code&gt;:strict&lt;/code&gt; with &lt;code&gt;:raise&lt;/code&gt;, write a little bit of error handling code to give the user a nice friendly message informing them which of the parameters they passed was wrong and why and I’m done! Shouldn’t take too long…&lt;/p&gt;

&lt;p&gt;After a couple of hours of work, I realized that due to the way that Strong Parameters is currently implemented, there is no easy way of telling whether the parameter was filtered out because the name wasn’t permitted or because the value wasn’t the correct type (which was the original issue  —  passing an integer when the filter only accepted an array). OK, no worries, for now let’s just display a general message informing which parameter was the problematic one, that should be enough to help the user figure out what’s wrong.&lt;/p&gt;

&lt;p&gt;So I added something like this in the base API controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UnpermittedParameters&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;error&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid parameter: %s. "&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sentence&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'Please verify that the parameter name is valid and the values are the correct type.'&lt;/span&gt;
  &lt;span class="n"&gt;render_error&lt;/span&gt; &lt;span class="s1"&gt;'param_error'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:bad_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:locals&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;:exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Excellent! Good day’s work, let’s open a PR and see what the CI says!&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;About an hour later…&lt;/p&gt;

&lt;p&gt;(we have an extensive test suite that is run against different Ruby versions and different databases)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jenkins reports that tests have failed. 636 failed tests to be exact. Yikes! I guess a lot of code was changed in the year-and-half since we made the change to Strong Parameters.&lt;/p&gt;

&lt;p&gt;Digging one more level down the Rails &lt;a href="https://github.com/rails/rails/blob/v5.1.4/actionpack/lib/action_controller/metal/strong_parameters.rb#L868"&gt;code&lt;/a&gt;, the &lt;code&gt;unpermitted_keys&lt;/code&gt; that raise an exception are calculated this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;unpermitted_keys&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;keys&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;always_permitted_parameters&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Luckily, turns out &lt;code&gt;always_permitted_parameters&lt;/code&gt; is actually another config option. Despite what its name suggests, it is actually a list of parameters that are filtered out but don’t raise an error when they are. By default, it is set to &lt;code&gt;%w( controller action )&lt;/code&gt;, since these two parameters are always sent by Rails, but it can easily be overridden in the application config. This is useful for parameters that you want to pass but don’t need to be assigned to the models —  for example, number of entries to display per page in index views. I added a few common parameters to this list, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;always_permitted_parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w(controller action format page per_page search locale utf8 _method authenticity_token locale _ie_support apiv id)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This got me down to 237 failing tests. Not bad for a quick, one line change.&lt;/p&gt;

&lt;p&gt;However, now the failing tests will need individual handling. Some may be easily fixed by adding some more common parameters that I didn’t think about to the list, but some of them are actually broken and have been for a while —  we just didn’t realize it because we were silently filtering out many parameters which may have caused the tests to fail. In the following clean-up, we may even find some new bugs that were missed because of this.&lt;/p&gt;

&lt;p&gt;On the positive side, getting this fixed properly will give us several benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Our users will receive informative feedback when they send an invalid parameter to the API. Even though at this point we still won’t be able to tell them what’s wrong, they will at least know which parameter(s) are causing issues, and won’t think an action succeeded when in fact some of the parameters were silently filtered out.&lt;/li&gt;
&lt;li&gt;Developers will have an easier time when debugging. Instead of wasting hours trying to figure out why the parameter they expected wasn’t available in the controller, they will get a clear exception that they can handle.&lt;/li&gt;
&lt;li&gt;Our tests will be better. Silently failing during tests leads us to an false feeling that everything is fine. Since we have a large test suite with good coverage (sadly not 100% yet), we often rely on tests when doing code review as an indication that new code isn’t introducing a regression.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I think any one of these is in itself worth my effort, not to mention getting all three at once.&lt;/p&gt;

&lt;p&gt;My key takeaways from all of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failing silently is a Bad Idea™. In this case, we missed the fact that after the change to Strong Parameters, the &lt;code&gt;:strict&lt;/code&gt; option no longer produces the desired outcome —  raising an error when filtering out parameters.&lt;/li&gt;
&lt;li&gt;Open Source is awesome. Thanks to the fact that the Rails code is open, I was able to figure out the cause of this bug in just a few hours.&lt;/li&gt;
&lt;li&gt;Don’t be afraid to dig in deep. Even if that means going into the code of some of your dependencies or their dependencies. I learned a whole lot in my career from digging deep -  both in terms of understanding our stack better and of reading some great code written by very talented developers. If you find a bug in some dependency’s code — fix it and send a PR! That way everyone will benefit from the fix, and you won’t have to create an ugly workaround in your code.&lt;/li&gt;
&lt;li&gt;Tests are important. Making sure they work the way you expect them to work is even more important. Don’t just check the request completed successfully, check that it did what it was supposed to do.&lt;/li&gt;
&lt;li&gt;Code review is important. If this issue was caught in code review 1.5 years ago, we wouldn’t have to fix hundreds of tests now. Even though the PR did undergo review, it was a huge PR — 257 files changed, which makes sense when changing such a fundamental aspect that touches practically every part of the application. However, such a large amount of changes makes it easy to miss something when reviewing. Whenever possible, small PRs are always better, as the chance that something will be missed is much smaller.&lt;/li&gt;
&lt;li&gt;Good documentation is important, especially if you are writing a library others depend on. Thanks to the Rails docs, I was quickly able to understand what each function does and identify which configuration options I need to set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading this! I would be happy for any feedback in the comments, or by DM on &lt;a href="https://twitter.com/tbrisker_pro"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. Thanks&lt;/em&gt; &lt;a href="https://medium.com/u/68a23010fc3d"&gt;&lt;em&gt;Dafna Rosenblum&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for writing the post that led me to write this one, about 2 years after I told myself I should start a blog.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>rails</category>
      <category>debugging</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
