<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Kiprosh Blogs]]></title><description><![CDATA[On our blog, we share our culture, processes, insights on scaling web & mobile apps, our learnings, etc. Our aim is to share and contribute back to the community.]]></description><link>https://blog.kiprosh.com/</link><image><url>https://blog.kiprosh.com/favicon.png</url><title>Kiprosh Blogs</title><link>https://blog.kiprosh.com/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Thu, 23 Apr 2026 14:29:52 GMT</lastBuildDate><atom:link href="https://blog.kiprosh.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Navigating Chrome Upgrades Beyond Version 115: CFT and Real Browser Testing Explained]]></title><description><![CDATA[Since the introduction of Google's Chrome For Testing in browser automation, it has emerged as the top choice for conducting automated browser tasks.]]></description><link>https://blog.kiprosh.com/navigating-chrome-upgrades-beyond-version-115-cft-and-real-browser-testing-explained/</link><guid isPermaLink="false">65db786366f7f2322306336b</guid><category><![CDATA[cft]]></category><category><![CDATA[chrome-for-testing]]></category><category><![CDATA[chromefortesting]]></category><category><![CDATA[chrome for testing]]></category><category><![CDATA[chrome]]></category><category><![CDATA[browser]]></category><category><![CDATA[webdriverio]]></category><category><![CDATA[wdio]]></category><category><![CDATA[automation]]></category><category><![CDATA[automated-tests]]></category><category><![CDATA[circleci]]></category><category><![CDATA[ci/cd]]></category><category><![CDATA[chromedriver]]></category><dc:creator><![CDATA[Mayank Shukla]]></dc:creator><pubDate>Fri, 17 Jan 2025 07:30:00 GMT</pubDate><media:content url="https://blog.kiprosh.com/content/images/2025/01/Group-1--1-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.kiprosh.com/content/images/2025/01/Group-1--1-.png" alt="Navigating Chrome Upgrades Beyond Version 115: CFT and Real Browser Testing Explained"><p>As mentioned in our previous <a href="https://blog.kiprosh.com/implement-dynamic-configuration-for-large-config-yml-file/">article</a>, we have a wdio-based automation project. We utilized Chrome <code>v108</code> for our end-to-end functional automation testing. However, given the current browser version being <code>&gt; v115</code> and since <a href="https://developer.chrome.com/blog/chrome-for-testing/">Chrome For Testing</a> was available for Chrome version <code>&gt;= v115</code>, we decided it would be beneficial to upgrade to <strong>Chrome For Testing (CFT)</strong> in our automation project.</p>
<p>Since the introduction of Google&apos;s Chrome For Testing, a new Chrome flavor that specifically targets web app testing and automation use cases, in browser automation, it has emerged as the top choice for conducting automated browser tasks.</p>
<h3 id="what-is-cft">What is CFT?</h3>
<hr>
<p>Chrome For Testing is a flavor of chrome browser which is specifically created for running automated test cases. It is a browser which is similar to chrome browser but with few key differences. First off, there is no automatic updating for the CFT browser. This makes it possible to execute automated scripts against a specific version of Chrome. The <a href="https://www.npmjs.com/package/@puppeteer/browsers">command-line utility</a> can be used to get the binaries for that particular version of CFT.</p>
<p><strong>Note</strong>:- <em>Chromedriver prior to version <code>115</code> are not integrated with chrome for testing infrastructure and need to be downloaded from <a href="https://developer.chrome.com/docs/chromedriver/downloads">Chromedriver Website</a></em></p>
<h3 id="setting-up-cft">Setting up CFT</h3>
<hr>
<p>We explored using &quot;Chrome For Testing&quot; in our WebdriverIO automation project. As mentioned in the article <a href="https://webdriver.io/blog/2023/07/31/driver-management/">Take a seat, WebdriverIO is driving for you!</a>, Now that CFT is included into WebdriverIO projects, you can run your automation script on a specific version of CFT by specifying browser name and version in the browser capabilities as demonstrated in the example below:</p>
<p><strong>wdio.conf.js</strong></p>
<pre><code class="language-js">export const config = {
  // ...
  capabilities: [{
    browserName: &apos;chrome&apos;,
    browserVersion: 125.0.6422.141 // Alternatively you can also specify only the initial part of version number e.g. 125. 
  }]
}
</code></pre>
<p>With the above config, WDIO will download the designated CFT and chromedriver version and execute tests.</p>
<p>In doing so, we eliminated all browser drivers and explicitly defined the Chrome version in the WDIO capabilities. This allowed WDIO to execute tests on the CFT instead of actual browsers. However, during local testing, we observed unexpected prompts such as &quot;<strong>Save password?</strong>&quot; appearing on the CFT, which were absent in real Chrome browsers. Additionally, a warning was displayed in the terminal. Please see the screenshot below for reference.</p>
<p><img src="https://blog.kiprosh.com/content/images/2025/01/console_warning-2.jpg" alt="Navigating Chrome Upgrades Beyond Version 115: CFT and Real Browser Testing Explained" loading="lazy"></p>
<h3 id="continue-to-use-real-chrome-browsers">Continue to use real Chrome Browsers</h3>
<hr>
<p>Due to the above behavior, we were uncertain whether employing CFT would be advantageous for our web application&apos;s end-to-end regression testing. So we dropped the idea of CFT and opted to utilize real Chrome browsers. However, we still had to address our issue with sudden Chrome browser upgrades, which were causing session creation failures with the message <code>This chromedriver only supports version &lt;version_number&gt;</code> &#x1F61E;. Through the insights gained from <a href="https://webdriver.io/blog/2023/07/31/driver-management/">this blog post</a>, we comprehended that starting from WebdriverIO version <code>v8.14.0</code> and beyond, we could eliminate the need for driver services.</p>
<p>We removed the following two dependencies from our <code>package.json</code></p>
<ul>
<li>chromedriver</li>
<li>wdio-chromedriver-service</li>
</ul>
<p>To run the WebdriverIO project with Chrome installed on your computer, you only need to specify the browser name in the browser capabilities.</p>
<pre><code class="language-js">export const config = {
  // ...
  capabilities: [{
    browserName: &apos;chrome&apos;,
  }]
}
</code></pre>
<p>After removing the aforementioned dependencies and updating the browser capabilities, WDIO started searching for the currently installed Chrome browser on our machine and downloaded the appropriate <code>chromedriver</code>.</p>
<h3 id="the-issue-remains-unresolved-%F0%9F%98%9E">The issue remains unresolved &#x1F61E;</h3>
<hr>
<p>After pushing this commit and executing our end-to-end regression tests according to the schedule, we encountered numerous spec failures with the <code>invalid session id</code> error. Our regression runs on <a href="https://circleci.com/">CircleCI</a>, as detailed in our <a href="https://blog.kiprosh.com/implement-dynamic-configuration-for-large-config-yml-file/">previous blog post</a>. Upon investigation, we identified that the latest Chrome browser version <code>v121 (121.0.6167.164)</code>, was being utilized. Further investigation revealed that while Chrome <code>v121</code> had been released, it was not sufficiently stable for automation purposes. Notably, it exhibited high memory usage due to memory leak, leading to frequent browser crashes.</p>
<p><img src="https://blog.kiprosh.com/content/images/2024/05/ram-usage.png" alt="Navigating Chrome Upgrades Beyond Version 115: CFT and Real Browser Testing Explained" loading="lazy"></p>
<p>We also encountered this <a href="https://issues.chromium.org/issues/41494493">issue</a> on Chromium and found a similar problem <a href="https://github.com/SeleniumHQ/selenium/issues/13533">here</a>.</p>
<h3 id="use-specific-stable-chrome-version">Use Specific Stable Chrome Version</h3>
<hr>
<p>In our CircleCI&apos;s configuration file, we specified Chrome version <code>118.0.5993.117</code></p>
<pre><code class="language-yaml">commands:
  install:
    steps:
      - checkout
      - run: sudo apt-get update
      - node/install-packages
      - browser-tools/install-chrome:
          replace-existing: true
          chrome-version: &quot;118.0.5993.117&quot;
</code></pre>
<p>With this adjustment, our automation specifications are now executing flawlessly on Chrome version <code>118</code>.</p>
<p><strong>Disclaimer</strong>:- <em>When we looked into CFT, the latest browser version was 121. Since then, newer versions of Chrome have been released, so you may not encounter the same issues. The primary purpose of this blog is to guide you on how to use a specific Chrome version and run tests on real browsers if you prefer not to use CFT.</em></p>
<h2 id="references"><strong>References</strong>:</h2>
<ul>
<li><a href="https://developer.chrome.com/blog/chrome-for-testing/">Chrome for Testing: reliable downloads for browser automation</a></li>
<li><a href="https://webdriver.io/blog/2023/07/31/driver-management/">Take a seat, WebdriverIO is driving for you!</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Optimizing CircleCI Configuration: Minimize config.yml size with Dynamic Configuration]]></title><description><![CDATA[We explored various approaches to reduce the size of our configuration file. Later, we came across Dynamic Configuration implementation of CircleCI.]]></description><link>https://blog.kiprosh.com/implement-dynamic-configuration-for-large-config-yml-file/</link><guid isPermaLink="false">648df95966f7f23223063082</guid><category><![CDATA[circleci]]></category><category><![CDATA[dynamic-configuration]]></category><category><![CDATA[ci/cd]]></category><dc:creator><![CDATA[Mayank Shukla]]></dc:creator><pubDate>Thu, 14 Sep 2023 09:22:16 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1593720218365-b2076cfdefee?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fFlNTCUyMENvbmZpZ3VyYXRpb24lMjBTZXR0aW5nc3xlbnwwfHx8fDE2OTQ0MDg2OTd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1593720218365-b2076cfdefee?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDh8fFlNTCUyMENvbmZpZ3VyYXRpb24lMjBTZXR0aW5nc3xlbnwwfHx8fDE2OTQ0MDg2OTd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Optimizing CircleCI Configuration: Minimize config.yml size with Dynamic Configuration"><p>We have a wdio-based automation project that does end-to-end functional testing. We use&#xA0;<a href="https://circleci.com/">CircleCI</a>&#xA0;as the CI/CD tool to run automation regression. When setting up a project with&#xA0;<code>CircleCI</code>, we need to enter all configuration details in&#xA0;the <code>config.yml</code>&#xA0;file under&#xA0;the <code>.circleci</code>&#xA0;folder. Earlier, we only had&#xA0;<code>staging regression</code>&#xA0;and&#xA0;<code>prod regression</code> jobs and their respective workflows in&#xA0;the <code>config.yml</code>&#xA0;file.</p>
<p>As mentioned in <a href="https://blog.kiprosh.com/visual-regression-using-wdio/">this article</a>, we also started doing screenshot testing for our application. With time, our configuration file expanded and became cluttered, accommodating various workflows like staging sanity, staging regression, prod regression, performance score testing, and screenshot testing jobs.</p>
<p>We explored various approaches to reduce the size of our configuration file. Later, we came across the <a href="https://circleci.com/docs/dynamic-config/">Dynamic Configuration</a> implementation of CircleCI. In the beginning, it appeared perplexing, but we eventually understood the purpose of using <code>continuation</code> and <code>path-filtering</code> orbs. In this article, we will share how we utilised the <code>continuation</code> orb to fulfil our requirements with CircleCI.</p>
<h3 id="what-were-the-requirements">What were the requirements?</h3>
<p>Below were our requirements:</p>
<ol>
<li>Weekly trigger&#xA0;<code>staging-regression</code>&#xA0;job on&#xA0;<code>staging</code>&#xA0;branch.</li>
<li>Weekly trigger&#xA0;<code>prod-regression</code>&#xA0;job on&#xA0;<code>master</code>&#xA0;branch.</li>
<li>Weekly trigger&#xA0;<code>staging-regression-other-features</code>&#xA0;job on&#xA0;<code>staging</code>&#xA0;branch.</li>
<li>Weekly trigger&#xA0;<code>prod-regression-other-features</code>&#xA0;job on&#xA0;<code>master</code>&#xA0;branch.</li>
<li>Weekly trigger&#xA0;<code>sign-up</code>,&#xA0;<code>registration</code>, and&#xA0;<code>setup</code>&#xA0;jobs on&#xA0;<code>staging</code>&#xA0;branch.</li>
<li>As per the need, we should be able to trigger screenshot testing and sanity jobs.</li>
</ol>
<h3 id="dynamic-configuration-implementation">Dynamic Configuration implementation</h3>
<hr>
<p>We created multiple YAML files for our different jobs and workflows. We used the <code>generate_config.js</code> script to choose the workflow file that we wanted to execute. We implemented the below-outlined structure for organising yml&#xA0;files for our different jobs.</p>
<p><img src="https://blog.kiprosh.com/content/images/2023/08/dynamic_config_diagram2.jpeg" alt="Optimizing CircleCI Configuration: Minimize config.yml size with Dynamic Configuration" loading="lazy"></p>
<h4 id="script-to-generate-a-new-configuration-yaml-file-based-on-the-parameter">Script to generate a new configuration YAML file based on the parameter</h4>
<p>We created a script to generate a new YAML configuration based on the job we want to execute. The new configuration file will be a child configuration file.</p>
<pre><code class="language-js">/** Below script is implemented in generate_config_script.js file*/

const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);

function readFile(fileName) {
  const filePath = path.join(__dirname, `${fileName}.yml`);

  return fs.readFileSync(filePath, &apos;utf8&apos;);
}

function generateCircleCIConfig() {
  const workflow = process.env.CONFIG_TXT;
  const workflowConfig = readFile(workflow);

  console.log(workflowConfig);
}

generateCircleCIConfig();
</code></pre>
<p>The above script is executed from the parent configuration file <code>config.yml</code> present in the <code>.circleci</code> folder.</p>
<pre><code class="language-yml">version: 2.1

setup: true

orbs:
  node: circleci/node@5.0.2
  continuation: circleci/continuation@0.1.2

parameters:
  config_txt:
    type: string
    default: staging_sanity

jobs:
  setup:
    docker:
      - image: cimg/node:18.16.1-browsers
    executor: continuation/default
    steps:
      - checkout
      - run:
          name: Generate config
          command: |
            node ./circleci-jobs/generate_config_script.js &gt; generated_config.yml
          environment:
            CONFIG_TXT: &lt;&lt; pipeline.parameters.config_txt &gt;&gt;
      - continuation/continue:
          configuration_path: generated_config.yml

workflows:
  setup:
    jobs:
      - setup
</code></pre>
<p>In the above parent&#xA0;<code>config.yml</code>&#xA0;file,</p>
<ol>
<li><code>setup: true</code>: This line indicates that the current&#xA0;<code>config.yml</code>&#xA0;file is a parent configuration file.</li>
<li>As mentioned in&#xA0;<a href="https://circleci.com/docs/using-dynamic-configuration/#a-basic-example">CI Documentation</a>,&#xA0;the <code>continuation</code>&#xA0;orb is used to execute the child configuration file created based on the output of&#xA0;<code>generate_config_script.js</code>.</li>
<li><code>config_txt</code>&#xA0;is a parameter that is passed to&#xA0;<code>generate_config_script.js</code>,&#xA0;based on which it creates a child configuration file. The default value is&#xA0;<code>staging_sanity</code>,&#xA0;so if no explicit parameter value is passed on CircleCI, then it by default triggers&#xA0;the <code>staging-sanity</code>&#xA0;job.</li>
</ol>
<p>Also, make sure to define the parameters declared in the parent <code>config.yml</code> file in the child <code>generated_config.yml</code> file as well. Otherwise, it will throw an error <code>Unexpected argument</code>. The official <a href="https://circleci.com/docs/dynamic-config/#how-dynamic-config-works">documentation</a> says <em>Pipeline parameters declared in the setup configuration must also be declared in the continuation configuration. These parameters can be used at continuation time.</em></p>
<p>In the parent <code>config.yml</code> file, only one workflow can be defined, which is responsible for generating the child configuration file. For our project, we required defining multiple workflows for our jobs, and to achieve the same with <code>dynamic configuration</code>, we used triggers.</p>
<h3 id="schedule-triggers-on-circleci">Schedule Triggers on CircleCI</h3>
<hr>
<ol>
<li>
<p>The next step was to create a <a href="https://circleci.com/docs/scheduled-pipelines/">scheduled pipeline</a> to periodically trigger a specific job.</p>
</li>
<li>
<p>Pipelines are scheduled to periodically run a specific <code>job</code> by adding a <code>trigger</code> for each of the jobs that need to be executed.</p>
</li>
<li>
<p>Triggers are added from the <code>Triggers Section</code> present under the <code>Project Settings</code> option for the <code>CircleCI Project</code>.</p>
</li>
</ol>
 <table>
  <tr>
    <td colspan="2" align="center"><img alt="Optimizing CircleCI Configuration: Minimize config.yml size with Dynamic Configuration" src="https://blog.kiprosh.com/content/images/2023/07/Triggers.png"> 
    </td>
  </tr>
 </table>
<ol start="4">
<li>For each of the jobs mentioned above, we created a new <code>Trigger</code> to periodically run a specific job.</li>
</ol>
<h4 id="challenges-faced-in-configuring-triggers">Challenges faced in configuring triggers</h4>
<p>We filled out the mandatory information needed to create triggers, but we faced challenges in deciding on the value for the field <strong>Repeats Per Hour</strong> for the trigger. We were unsure whether this field would trigger the job a certain number of times every hour based on the field&apos;s name. We intended our trigger to run just once on the designated day. When we got in touch with <code>CircleCI support</code>, they clarified that the field decides how many times a trigger would be executed in each hour chosen in the <strong>Start Time (UTC)</strong> selection.</p>
<p>For e.g., if the selected &quot;Start Time (UTC)&quot; is <code>3:00 (UTC)</code> and <code>7:00 UTC</code> and &quot;Repeats Per Hour&quot; is set to <code>2</code>, then the trigger will be executed twice between each selected time, i.e., it will be executed twice between <code>3:00 (UTC)</code> and <code>4:00 (UTC)</code> and twice between <code>7:00 UTC</code> and <code>8:00 (UTC)</code>.</p>
<p>As mentioned in the <a href="https://circleci.com/docs/scheduled-pipelines/#scheduled-pipeline-run-later">CI Documentation</a>, setting the start time for a trigger does not guarantee the job will be triggered precisely at the &quot;Start Time&quot; selected. This means that for a trigger, when we select &quot;Start Time&quot; as <code>6:00 AM UTC</code> and &quot;Repeat Per Hour&quot; as <code>1</code>, the job will be triggered once between <code>6:00 AM UTC</code> and <code>7:00 AM UTC</code> and will not always be triggered exactly at <code>6:00 AM UTC</code>.</p>
<p>Next, we declared the pipeline parameter named <code>config_txt</code> for our trigger and set its value to the <code>&lt;child_job&gt;.yml</code> file name containing the workflow that we wanted to trigger. For instance, in order to trigger the <code>production regression</code> workflow, we set the <code>config_txt</code> parameter value to <code>prod_regression</code>. Similarly, we created triggers for each workflow that we wanted to execute.</p>
<p>We thought the trigger was scheduled for now. However, the trigger did not execute as scheduled. There was an error <code>block-unregistered-user</code> in our workflow. On investigation, we noticed that for all the triggers, we have &quot;Attribution Actor&quot; set as <code>Scheduled Actor (Scheduling System)</code>, which is the default. As mentioned in the <a href="https://support.circleci.com/hc/en-us/articles/4413968995099-How-to-set-up-the-Prevent-unregistered-user-spend-feature">article</a>, if the option &quot;Prevent unregistered user spend&quot; is turned <code>ON</code> under the &quot;Usage Control&quot; tab present in the &quot;Plan&quot; section of the project, then the workflow won&apos;t be triggered for unregistered users and error <code>block-unregistered-user</code> will be displayed.</p>
<p>This was exactly the case with our project, and to resolve the issue, we selected &quot;Attribution Actor&quot; as one of the users registered in the project. With this, we were able to trigger and execute our workflow successfully.</p>
<p>We have created a demo project <a href="https://github.com/darshanshah1996/dynamic-configuration-demo">circleci-dynamic-configuration-demo</a> that you can clone and explore for better understanding.</p>
<p><strong>References:</strong></p>
<ul>
<li><a href="https://circleci.com/docs/dynamic-config/">Dynamic Configuration in CircleCI</a></li>
<li><a href="https://circleci.com/docs/using-dynamic-configuration/#a-basic-example"><code>continuation</code> orb</a></li>
<li><a href="https://circleci.com/docs/dynamic-config/#how-dynamic-config-works">How Dynamic config works</a></li>
<li><a href="https://circleci.com/docs/scheduled-pipelines/">Scheduled pipelines</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Type-checking in Ruby 3 using RBS]]></title><description><![CDATA[RBS is a new syntax language for dynamic typing introduced in Ruby 3 which can be used to describe the data types in a Ruby class. It can be used with a type-checker like Steep to do type checking.]]></description><link>https://blog.kiprosh.com/type-checking-in-ruby-3-using-rbs/</link><guid isPermaLink="false">63e7b26a66f7f232230623fb</guid><category><![CDATA[ruby3]]></category><category><![CDATA[rbs]]></category><category><![CDATA[ruby]]></category><category><![CDATA[rails]]></category><category><![CDATA[steep]]></category><category><![CDATA[type-checking]]></category><category><![CDATA[dynamic-typing]]></category><dc:creator><![CDATA[Navaneeth Krishnan P]]></dc:creator><pubDate>Wed, 05 Jul 2023 07:17:29 GMT</pubDate><content:encoded><![CDATA[<p>Ruby 3 has introduced a new syntax language for dynamic typing called RBS. In short, it is a language that we can use to describe the data types used in a Ruby class. We can define the data type of variables and the return type of methods used in a class using RBS.</p><!--kg-card-begin: markdown--><h2 id="why-do-we-need-type-checking">Why do we need type-checking?</h2>
<!--kg-card-end: markdown--><p>Since Ruby is a dynamically typed language, we don&apos;t need to define the data type of the variables we are using. Ruby automatically assigns a type based on the variable&apos;s value at runtime. Let&apos;s take the below class as an example.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby"># basic_math.rb

class BasicMath
  def initialize(num1, num2)
    @num1 = num1
    @num2 = num2
  end

  def first_less_than_second?
    @num1 &lt; @num2
  end

  def add
    @num1 + @num2
  end
end
</code></pre>
<!--kg-card-end: markdown--><p>If we run the below code it will return <code>false</code> as expected since integer <code>1000</code> is not less than integer <code>2</code>.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby">puts BasicMath.new(1000, 2).first_less_than_second?
 
=&gt; false
</code></pre>
<!--kg-card-end: markdown--><p>We did not define that <code>num1</code> and <code>num2</code> are integers. Ruby interpreter automatically assigned <code>integer</code> type to them at runtime. And the method <code>first_less_than_second?</code> checks if its first argument is less than the second one. But let&apos;s call the same method with strings as parameters.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby">puts BasicMath.new(&apos;1000&apos;, &apos;2&apos;).first_less_than_second?
 
=&gt; true
</code></pre>
<!--kg-card-end: markdown--><p>Here the output is <code>true</code> because <code>num1</code> and <code>num2</code> are strings. But for this method we expected the output to be <code>false</code>. This error happened because we missed doing a string-to-integer conversion on the parameters. A type-checking system will help us identify such errors.</p><!--kg-card-begin: markdown--><h2 id="how-to-write-an-rbs-file">How to write an RBS file?</h2>
<!--kg-card-end: markdown--><p>Let&apos;s write an RBS file for the above <code>BasicMath</code> class.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby"># basic_math.rbs

class BasicMath
  @num1: Integer
  @num2: Integer

  def initialize: (Integer num1, Integer num2) -&gt; void
  def first_less_than_second?: -&gt; bool
  def add: -&gt; Integer
end
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><ul>
<li>For the variables <code>@num1</code> and <code>@num2</code>, we define its type using the <code>:</code> symbol.</li>
<li>For methods, we write the method name followed by a <code>:</code> symbol and then specify the types of each method argument inside the <code>()</code> bracket. Then we define the return type using a <code>-&gt;</code> symbol.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="how-to-run-an-rbs-file">How to run an RBS file?</h2>
<!--kg-card-end: markdown--><p>We only defined the structure of the <code>BasicMath</code> class in our RBS file. We still need to run it against a type checker to verify if the types are correct in our class. RBS is only a language that defines the structure of a class. It cannot do type-checking on its own. Let&apos;s use a popular type checker called <code>steep</code> to test our class against the RBS file.</p><!--kg-card-begin: markdown--><h2 id="setup-steep-gem">Setup steep gem</h2>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><ul>
<li>Install steep gem using <code>gem install steep</code>.</li>
<li>Run <code>steep init</code> to generate the configuration file for steep.</li>
<li>This will generate a configuration file called <code>Steepfile</code>.</li>
<li>Add the following code to the <code>Steepfile</code>.</li>
</ul>
<pre><code class="language-ruby">target :app do
  check &quot;lib&quot;
  signature &quot;sig&quot;

  library &quot;set&quot;, &quot;pathname&quot;
end
</code></pre>
<ul>
<li>Add the directory of the <code>.rb</code> files in <code>check</code> option and directory of <code>.rbs</code> files in <code>signature</code> option.</li>
<li>In this example, I am adding <code>.rb</code> files in <code>lib</code> directory and <code>.rbs</code> files in <code>sig</code> directory.</li>
<li>Make sure the file names of the respective <code>.rb</code> and <code>.rbs</code> files are the same.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="type-checking-using-steep">Type-checking using Steep</h2>
<!--kg-card-end: markdown--><p>Run steep using the command <code>steep check</code>. This will perform type-checking on all the <code>Ruby</code> files in <code>lib</code> directory, using their respective <code>RBS</code> files in <code>sig</code> directory.<br>If the type-checking passes it will return the below message.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby">steep check

# Type checking files:

....................................................................................

No type error detected. &#x1FAD6;
</code></pre>
<!--kg-card-end: markdown--><p>If there is any mismatch in the types, it will throw an error message. For example, in <code>BasicMath</code> class, let&apos;s change the return value of <code>first_less_than_second?</code> to a <code>String</code>. In our RBS file, we have defined that the output of this method will be a <code>Boolean</code>.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby"># lib/basic_math.rb

class BasicMath
  def initialize(num1, num2)
    @num1 = num1
    @num2 = num2
  end

  def first_less_than_second?
    @num1 &lt; @num2
    &apos;yes&apos;
  end

  def add
    @num1 + @num2
  end
end
</code></pre>
<!--kg-card-end: markdown--><p>Now if we run <code>steep check</code>, it will return an error message. This is because according to the RBS definition <code>first_less_than_second?</code> method is expected to return a <code>Boolean</code> value.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby">steep check

# Type checking files:

.......................................................................F............

lib/rbs_test.rb:7:6: [error] Cannot allow method body have type `::String` because declared as type `bool`
&#x2502;   ::String &lt;: bool
&#x2502;     ::String &lt;: (true | false)
&#x2502;       ::String &lt;: true
&#x2502;
&#x2502; Diagnostic ID: Ruby::MethodBodyTypeMismatch
&#x2502;
&#x2514;   def first_less_than_second?
        ~~~~~~~~~~~~~~~~~~~~~~~

Detected 1 problem from 1 file
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="typeprof">TypeProf</h2>
<!--kg-card-end: markdown--><p>TypeProf is a type analysis tool that comes built-in with Ruby 3. It can automatically detect data types in a class and return a rough RBS file for it.<br>Just run the command <code>typeprof &lt;filename&gt;</code> and it will return a rough RBS file. Let&apos;s try running the <code>typeprof</code> command on the above <code>BasicMath</code> class.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby">typeprof lib/basic_math.rb
</code></pre>
<!--kg-card-end: markdown--><p>This will return the following result.</p><!--kg-card-begin: markdown--><pre><code class="language-ruby"># TypeProf 0.21.3

# Classes
class BasicMath
  @num1: untyped
  @num2: untyped

  def initialize: (untyped num1, untyped num2) -&gt; void
  def first_less_than_second?: -&gt; untyped
  def add: -&gt; untyped
end
</code></pre>
<!--kg-card-end: markdown--><p>If we check the returned RBS code, we can see that TypeProf assigned <code>untyped</code> as the data type in some places. This is because TypeProf couldn&apos;t determine the correct data type at those places. But it did return a skeletal structure for the RBS file which we can edit according to the way we want. TypeProf is still an experimental tool, so it doesn&apos;t always return an accurate RBS file.</p><!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<!--kg-card-end: markdown--><p>We saw how we can write an RBS file for a class and do type-checking using <code>Steep</code>. We also saw how we can use <code>TypeProf</code> to generate a rough RBS file for a class. Since RBS allows us to add type definitions, it can help in catching type errors in our code. So it is a good practice to write RBS for our Ruby code.</p><!--kg-card-begin: markdown--><h2 id="references">References</h2>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><ul>
<li><a href="https://www.honeybadger.io/blog/ruby-rbs-type-annotation/">honeybadger - Understanding RBS, Ruby&apos;s new Type Annotation System</a></li>
<li><a href="https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in-action.html">appsignal - RBS: A New Ruby 3 Typing Language in Action</a></li>
<li><a href="https://medium.com/whitespectre/an-exploration-of-rbs-by-ruby-is-it-production-ready-c1eb86530154">Medium - An Exploration of RBS by Ruby: Is it Production-Ready?</a></li>
<li><a href="https://github.com/soutaro/steep">steep gem</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Rails 7.1 ActiveRecord adds normalizes API for attribute values]]></title><description><![CDATA[Rails 7.1 adds ActiveRecord::Base::normalizes API to normalize attribute values to a common format before saving them to the database.]]></description><link>https://blog.kiprosh.com/rails-7-1-activerecord-adds-normalizes-api/</link><guid isPermaLink="false">63a43aff66f7f23223061912</guid><category><![CDATA[rails 7]]></category><category><![CDATA[rails 7.1]]></category><category><![CDATA[active_record]]></category><category><![CDATA[normalize data]]></category><dc:creator><![CDATA[Sampat Badhe]]></dc:creator><pubDate>Wed, 17 May 2023 09:46:43 GMT</pubDate><media:content url="https://blog.kiprosh.com/content/images/2023/03/normalize_data--17--1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.kiprosh.com/content/images/2023/03/normalize_data--17--1.png" alt="Rails 7.1 ActiveRecord adds normalizes API for attribute values"><p>Sometimes you may want to standardize the data before saving it to the database. For example, downcasing emails, removing any leading and trailing whitespaces, and so on. <code>Rails 7.1</code> adds <a href="https://github.com/rails/rails/pull/43945"><code>ActiveRecord::Base::normalizes API</code></a>, which allows you to normalize attribute values to a common format before saving them to the database. This can help improve the integrity and consistency of your data and make it simpler to query records based on attribute values.</p>
<h2 id="before-rails-71">Before Rails 7.1</h2>
<ul>
<li>
<p>Normalizing it in <code>before_validation</code> or <code>before_save</code> callback</p>
<pre><code class="language-ruby">class User &lt; ActiveRecord::Base
  before_validation do
    self.name = name.downcase.titleize # normalize_name
    self.email = email.strip.downcase # normalize_email
  end

  OR

  before_save do
    self.name = name.downcase.titleize # normalize_name
    self.email = email.strip.downcase # normalize_email
  end
end
</code></pre>
</li>
<li>
<p>Normalizing by overriding <code>attribute_writer</code></p>
<pre><code class="language-ruby">class User &lt; ActiveRecord::Base
  def name=(val)
    self.name = val.downcase.titleize
  end

  def email=(val)
    self.name = email.strip.downcase
  end
end
</code></pre>
</li>
</ul>
<h2 id="rails-71-onwards">Rails 7.1 onwards</h2>
<pre><code class="language-ruby">class User &lt; ActiveRecord::Base
  normalizes :name, with: -&gt; name { name.downcase.titleize }
  normalizes :email, with: -&gt; email { email.downcase.strip }
end

3.0.0 :001 &gt; user = User.create(name: &apos;BOB&apos;, email: &quot; BOB@EXAMPLE.COM\n&quot;)
  TRANSACTION (0.1ms)  begin transaction
  User Create (2.0ms)  INSERT INTO &quot;users&quot; (&quot;name&quot;, &quot;email&quot;) VALUES (?, ?)  [[&quot;name&quot;, &quot;Bob&quot;], [&quot;email&quot;, &quot;bob@example.com&quot;]]
  TRANSACTION (1.9ms)  commit transaction
3.0.0 :002 &gt; user.name
 =&gt; &quot;Bob&quot;
3.0.0 :003 &gt; user.email
 =&gt; &quot;bob@example.com&quot;
</code></pre>
<p>By default, the normalization will not be applied to <code>nil</code> values. This behavior can be changed with the <code>apply_to_nil</code> option which defaults to <code>false</code>.</p>
<pre><code class="language-ruby">class User &lt; ActiveRecord::Base
  normalizes :name, with: -&gt; name { name&amp;.downcase&amp;.titleize || &apos;Untitled&apos; }, apply_to_nil: true
end

3.0.0 :004 &gt; user = User.create(name: nil)
  TRANSACTION (0.1ms)  begin transaction
  User Create (1.4ms)  INSERT INTO &quot;users&quot; (&quot;name&quot;, &quot;email&quot;) VALUES (?, ?)  [[&quot;name&quot;, &quot;Untitled&quot;], [&quot;email&quot;, nil]]
  TRANSACTION (1.2ms)  commit transaction
3.0.0 :005 &gt; user.name
 =&gt; &quot;Untitled&quot;
</code></pre>
<p>You can also pass multiple attributes with the <code>normalizes</code> method to apply normalization to all of them.</p>
<pre><code class="language-ruby">class User &lt; ActiveRecord::Base
  normalizes :first_name, :last_name, :title, with: -&gt; attribute { attribute.strip }
end
</code></pre>
<p>The normalization is also applied to the corresponding keyword argument of finder methods. This allows a record to be created and later queried using denormalized values.</p>
<pre><code class="language-ruby">3.0.0 :006 &gt; user = User.find_by(email: &quot;\tBOB@EXAMPLE.COM &quot;)
  User Load (0.2ms)  SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;email&quot; = ? LIMIT ?  [[&quot;email&quot;, &quot;bob@example.com&quot;], [&quot;LIMIT&quot;, 1]]
3.0.0 :007 &gt; user.email
 =&gt; &quot;bob@example.com&quot;
3.0.0 :008 &gt; User.exists?(email: &quot;\tBOB@EXAMPLE.COM &quot;)
  User Exists? (0.3ms)  SELECT 1 AS one FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;email&quot; = ? LIMIT ?  [[&quot;email&quot;, &quot;bob@example.com&quot;], [&quot;LIMIT&quot;, 1]]
 =&gt; true
</code></pre>
<p>It will not be applied when we pass it as a raw query.</p>
<pre><code class="language-ruby">3.0.0 :009 &gt; User.exists?([&quot;email = ?&quot;, &quot;\tBOB@EXAMPLE.COM &quot;])
  User Exists? (0.2ms)  SELECT 1 AS one FROM &quot;users&quot; WHERE (email = &apos; BOB@EXAMPLE.COM &apos;) LIMIT ?  [[&quot;LIMIT&quot;, 1]]
 =&gt; false
</code></pre>
<p>These changes also introduced the <code>normalize_value_for</code> class method which returns the normalized value for the given attribute.</p>
<pre><code class="language-ruby">3.0.0 :010 &gt; User.normalize_value_for(:email, &quot;\tBOB@EXAMPLE.COM &quot;)
 =&gt; &quot;bob@example.com&quot;
</code></pre>
<p><strong>How to apply normalization to existing records?</strong></p>
<p>The normalization is applied when the attribute is assigned or updated. This means that if a record persisted before the normalization was declared, the record&apos;s attribute will not be normalized until it is assigned a new value. Thus, the alternative is to explicitly migrate via <code>Normalization#normalize_attribute</code>.</p>
<pre><code class="language-ruby">  class User &lt; ActiveRecord::Base
    normalizes :email, with: -&gt; email { email&amp;.downcase&amp;.strip }
  end

  User.find_each do |legacy_user|
    # legacy_user.email # =&gt; &quot; BOB@EXAMPLE.COM\n&quot;
    legacy_user.normalize_attribute(:email)
    # legacy_user.email # =&gt; &quot;bob@example.com&quot;
    legacy_user.save
  end
</code></pre>
<p><em><strong>Note:</strong> The normalization may be applied multiple times, so it should be <strong>idempotent</strong>. In other words, applying the normalization more than once should have the same result as applying it only once.</em></p>
<h3 id="references">References</h3>
<ul>
<li><a href="https://github.com/rails/rails/pull/43945">Add ActiveRecord::Base::normalizes</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Setup Image Comparison Service of WDIO to make UI Testing more impactful]]></title><description><![CDATA[This blog helps to configure visual regression testing using wdio-image-comparison-service.]]></description><link>https://blog.kiprosh.com/visual-regression-using-wdio/</link><guid isPermaLink="false">63e7318e66f7f23223062399</guid><category><![CDATA[webdriverio]]></category><category><![CDATA[wdio-image-comparison-service]]></category><category><![CDATA[visual-regression]]></category><category><![CDATA[screenshot-testing]]></category><category><![CDATA[ui-testing]]></category><category><![CDATA[javascript]]></category><category><![CDATA[wdio]]></category><dc:creator><![CDATA[Mayank Shukla]]></dc:creator><pubDate>Wed, 03 May 2023 08:03:42 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1606296149205-69c6c18fa499?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1pc21hdGNofGVufDB8fHx8MTY4MjA3MDMxOA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1606296149205-69c6c18fa499?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1pc21hdGNofGVufDB8fHx8MTY4MjA3MDMxOA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Setup Image Comparison Service of WDIO to make UI Testing more impactful"><p>In the previous <a href="https://blog.kiprosh.com/webdriverio-elements-chaining/">article</a>, we talked about <a href="https://webdriver.io/docs/gettingstarted/">WebdriverIO</a>. A tool that has become popular very fast in the field of test automation due to its powerful advantages. In this article, we will talk about one of its advantages that has advanced our test automation suite. We have an application that helps our clients develop stunning websites. Whenever a new feature is developed that may impact the UI of the site, WebdriverIO can help us test the change in the UI of the sites. WebdriverIO provides a service called as <em><a href="https://webdriver.io/docs/wdio-image-comparison-service/">wdio-image-comparison-service</a></em>, which performs <strong>pixel-by-pixel</strong> image comparison.</p>
<p>We already have a <strong>wdio-based</strong> automation project that does end-to-end functional testing. We expanded it to  screenshot test all the pages for thousands of sites. We explored various tools for image comparison. We then discovered that WebdriverIO furthermore offers an image comparison tool. Then we began looking into how we could incorporate this &quot;wdio-image-comparison-service&quot; into our already-running automation project.</p>
<h3 id="set-up">Set-up</h3>
<p>Referring to the official <a href="https://webdriver.io/docs/wdio-image-comparison-service/">doc</a> for <code>wdio-image-comparison-service</code><em>,</em> we installed the module:</p>
<pre><code class="language-js">npm install --save wdio-image-comparison-service
</code></pre>
<p>We added the below options to the Test runner services in <code>wdio.conf.js</code> file:</p>
<pre><code class="language-js">services: [
  &apos;chromedriver&apos;,
  [
    &apos;image-comparison&apos;,
    // The options
    {
      // Some options, see the docs for more
      baselineFolder: join(process.cwd(), &apos;./tests/&apos;),
      formatImageName: &apos;{tag}-{logName}-{width}x{height}&apos;,
      screenshotPath: join(process.cwd(), &apos;.tmp/&apos;),
      savePerInstance: true,
      autoSaveBaseline: true,
      blockOutStatusBar: true,
      blockOutToolBar: true,
      disableCSSAnimation: false,
      ignoreColors: true,
      ignoreTransparentPixel: true,
    },
  ],
],
</code></pre>
<p>Above is the default setup. With this default setup, we thought that we had completed configuring the setup and the next step was to create a spec file to compare screenshots.</p>
<ol>
<li><code>browser.saveScreen(&quot;homepage&quot;)</code> function saves the screenshot as a baseline image with the name <code>homepage.png</code>.</li>
<li><code>browser.checkScreen(&quot;homepage&quot;)</code> function saves the screenshot of the current page and compares it with the baseline image having the same name i.e. <code>homepage.png</code>.</li>
</ol>
<p>However, we encountered an issue with the default setup. When we attempted to create a spec to compare screenshots of the two different doodles, it did not detect any mismatches, and the spec passed. We will explain this issue in more detail below:</p>
<p>We created a spec file called <code>image_compare.js</code> as follows:</p>
<pre><code class="language-js">describe(&quot;Image Comparison&quot;, () =&gt; {
  it(&quot;saves the baseline image&quot;, async () =&gt; {
    await browser.url(&quot;https://www.google.com/doodles/teachers-day-2021-september-11&quot;);
    await browser.saveScreen(&quot;homepage&quot;);
  });

  it(&quot;compares with baseline image&quot;, async () =&gt; {
    await browser.url(&quot;https://www.google.com/doodles/teachers-day-2022-september-11&quot;);

    const result = await browser.checkScreen(&quot;homepage&quot;);

    expect(result).toEqual(0);
  });
});
</code></pre>
<p>In the above spec, we were comparing screenshots of the <code>Teacher&apos;s Day - 2021</code> homepage and <code>Teacher&apos;s Day - 2022</code> homepage. And we were expecting some mismatch percentage. But the spec got passed. When we investigated, we found that <code>wdio-image-comparison-service</code> saved the baseline image under <code>.tmp</code> folder. The <code>.tmp</code> folder has <code>actual</code> as a subfolder. Under the <code>actual</code> folder, it saved the baseline image.</p>
<p>In our case, when we ran the spec, it overrode Teacher&apos;s Day - 2021 <code>homepage.png</code> with Teacher&apos;s Day - 2022 <code>homepage.png</code> inside <code>.tmp/</code> folder. Inside <code>./tests/</code> folder also, it saved Teacher&apos;s Day - 2022 <code>homepage.png</code>. When we investigated more on this, then we got to know that internally this package uses <code>fs-extra</code> plugin and it has limited Windows support. But, running the same script on Mac or Linux did not result in any issues or failures. We also found out that this is an existing <a href="https://github.com/wswebcreation/wdio-image-comparison-service/issues/82">issue</a>. We have also added a <a href="https://github.com/wswebcreation/wdio-image-comparison-service/issues/82#issuecomment-1040224972">comment</a> there about what could be a workaround.</p>
<h3 id="workaround">Workaround</h3>
<p>As we previously said, the baseline picture is saved by &apos;wdio-image-comparison-service&apos; by default under the &apos;.tmp&apos; folder. In &apos;wdio.conf.js&apos;, we removed the following code from the&apos;services&apos; section:</p>
<pre><code class="language-js">baselineFolder: join(process.cwd(), &apos;./tests/&apos;),
screenshotPath: join(process.cwd(), &apos;.tmp/&apos;),
</code></pre>
<p>Then, we specified the <code>testOptions</code> in the <code>it</code> block where screenshots are compared.</p>
<pre><code class="language-js">const testOptions = {
  actualFolder: join(process.cwd(), &apos;./.tmp/checkActual&apos;),
  baselineFolder: join(process.cwd(), &apos;./.tmp/actual&apos;),
  diffFolder: join(process.cwd(), &apos;./.tmp/testDiff&apos;),
  returnAllCompareData: true,
};
</code></pre>
<ul>
<li><code>checkActual</code>: The current image will be saved in this folder. It is called as <code>actualFolder</code>.</li>
<li><code>actual</code>: The baseline image will be saved in this folder. It is called as <code>baselineFolder</code>.</li>
<li><code>diffFolder</code>: This image gets created with all the mismatches highlighted after the two screenshots are compared.</li>
<li><code>returnAllCompareData</code>: By default, <code>check</code> methods of this service returns only mismatch percentage. When this option is passed as <code>true</code>, then it returns the current image <code>filename</code>, <code>folders</code> that contain the path of <code>actual</code>, <code>baseline</code>, and <code>diff</code>, and <code>misMatchPercentage</code>.</li>
</ul>
<p>After these modifications, the updated code is as follows:</p>
<p><code>wdio.conf.js</code></p>
<pre><code class="language-js">services: [
  &apos;chromedriver&apos;,
  [
    &apos;image-comparison&apos;,
    // The options
    {
      // Some options, see the docs for more
      savePerInstance: true,
      autoSaveBaseline: true,
      blockOutStatusBar: true,
      blockOutToolBar: true,
      disableCSSAnimation: false,
      ignoreColors: true,
      ignoreTransparentPixel: true,
    },
  ],
],
</code></pre>
<p><code>image_compare.js</code></p>
<pre><code class="language-js">import { join } from &apos;path&apos;;

describe(&quot;Image Comparison&quot;, () =&gt; {
  it(&quot;saves the baseline image&quot;, async () =&gt; {
    await browser.url(&quot;https://www.google.com/doodles/teachers-day-2021-september-11&quot;);
    await browser.saveScreen(&quot;homepage&quot;);
  });

  it(&quot;compares with baseline image&quot;, async () =&gt; {
    await browser.url(&quot;https://www.google.com/doodles/teachers-day-2022-september-11&quot;);
    
    const testOptions = {
      actualFolder: join(process.cwd(), &apos;./.tmp/checkActual&apos;),
      baselineFolder: join(process.cwd(), &apos;./.tmp/actual&apos;),
      diffFolder: join(process.cwd(), &apos;./.tmp/testDiff&apos;),
      returnAllCompareData: true,
    };

    const result = await browser.checkScreen(&quot;homepage&quot;, testOptions);

    expect(result).toEqual(0);
  });
});
</code></pre>
<p>When we passed <code>testOptions</code> as a parameter to the <code>checkScreen</code> function, then <code>wdio</code> easily identified where our baseline images are saved and with which image to compare.</p>
<p>Now, if you run the above spec, then it will properly do the comparison, highlight the mismatch, and return the mismatch percentage.</p>
<table>
  <tr>
      <td align="center"><b>Teacher&apos;s Day - 2021</b></td>
      <td align="center"><b>Teacher&apos;s Day - 2022</b></td>
  </tr>
  <tr>
    <td><img alt="Setup Image Comparison Service of WDIO to make UI Testing more impactful" src="https://blog.kiprosh.com/content/images/2023/04/homepage-chrome-1050x652-dpr-1.5-1.png"></td>
    <td><img alt="Setup Image Comparison Service of WDIO to make UI Testing more impactful" src="https://blog.kiprosh.com/content/images/2023/04/homepage-chrome-1050x652-dpr-1.5-2.png"></td>
  </tr>
  <tr>
      <td colspan="2" align="center"><b>Image Difference</b></td>
  </tr>
  <tr>
    <td colspan="2" align="center"><img alt="Setup Image Comparison Service of WDIO to make UI Testing more impactful" src="https://blog.kiprosh.com/content/images/2023/04/homepage-chrome-1050x652-dpr-1.5-3.png"></td>
  </tr>
</table>
<p>The <code>wdio-image-comparison-service</code> is not just limited to capturing and comparing screens, it can also compare the full page screen, and elements, and also checks if the website supports the keyboard&apos;s <code>TAB</code> key. Please refer to <code>wdio-image-comparison-service</code> <a href="https://github.com/wswebcreation/wdio-image-comparison-service/blob/HEAD/README.md">options</a>.</p>
<h3 id="mobile-screenshot-testing">Mobile Screenshot Testing</h3>
<p>If screenshot testing for mobile devices is needed, then just resizing your browser is never a good idea because this module compares visuals of what the end user is going to see. <a href="https://webdriver.io/docs/devtools-service/">Wdio Devtools</a> can be helpful in such cases. <code>Wdio Devtools</code> is in itself a distinct, extensive subject that offers additional benefits.</p>
<p>We just need to pass <code>service</code> as <code>devtools</code> in <code>wdio.conf.js</code> file. We can emulate a specific device by <code>await browser.emulateDevice(&apos;iPhone X&apos;)</code>. Please refer to <a href="https://webdriver.io/docs/devtools-service/#device-emulation">Device emulation</a>.</p>
<p>We have created a small demo project <a href="https://github.com/mayankshukla94/wdio-image-comparison-demo">wdio-image-comparison-demo</a> that you can clone for better understanding. You can explore other <a href="https://github.com/wswebcreation/wdio-image-comparison-service/blob/HEAD/README.md">options</a> available for <code>wdio-image-comparison-service</code>.</p>
<p><strong>References:</strong></p>
<ul>
<li><a href="https://webdriver.io/docs/wdio-image-comparison-service/">Image Comparison (Visual Regression Testing) Service</a></li>
<li><a href="https://github.com/wswebcreation/wdio-image-comparison-service/blob/HEAD/README.md"><code>wdio-image-comparison-service</code> options</a></li>
<li><a href="https://github.com/wswebcreation/wdio-image-comparison-service/issues/82"><code>wdio-image-comparison-service</code> issue</a></li>
<li><a href="https://webdriver.io/docs/devtools-service/#device-emulation">Wdio Devtools Device Emulation</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[All about "Data" Simple Immutable Value Objects in Ruby 3.2]]></title><description><![CDATA[Ruby 3.2 adds Data to define simple immutable value objects, which are designed to be small, self-contained, and represent a single concept.]]></description><link>https://blog.kiprosh.com/ruby-3-2-data-simple-immutable-value-objects/</link><guid isPermaLink="false">640496e666f7f232230627d6</guid><category><![CDATA[ruby3]]></category><category><![CDATA[Ruby on Rails]]></category><category><![CDATA[Data]]></category><category><![CDATA[Value Object]]></category><category><![CDATA[ruby]]></category><dc:creator><![CDATA[Vishal Jain]]></dc:creator><pubDate>Fri, 24 Mar 2023 07:30:50 GMT</pubDate><content:encoded><![CDATA[<p>In Ruby 3.2, a new class <code>Data</code> was introduced as a way to define <a href="https://github.com/ruby/ruby/pull/6353">simple immutable value objects</a>. A <a href="https://martinfowler.com/bliki/ValueObject.html">value object</a> is a type of object that represents a value in a program, such as a point in 2D space or a date. The main advantage of value objects is that they are easy to understand, simple to use, and can improve the readability and maintainability of code. The proposal to add <code>Data</code> class was accepted by Matz on the <a href="https://bugs.ruby-lang.org/issues/16122#note-49">Ruby forum here</a>.</p><h3 id="how-does-it-work">How does it work?</h3><p>Using the newly defined class <code>Data</code> we can create a simple immutable object. These objects are designed to be small, self-contained, and represent a single concept in your application. <br>The class definition includes the name of the class, as well as a list of instance variables that the object will contain. Here&apos;s an example:</p><pre><code class="language-ruby">class Point &lt; Data.define(:x, :y)
end

# Can be initialized using Positional or Keyword arguments
point = Point.new(3, 4) OR Point.new(x: 3, y: 4) OR Point[3, 4] OR Point[x: 3, y: 4]
=&gt; #&lt;data Point x=3, y=4&gt;

# But using both Positional and Keyword arguments together will not work
Point.new(2, y: 3)
=&gt; `new`: wrong number of arguments (given 2, expected 0) (ArgumentError)</code></pre><p>Once an instance of the object is created, its instance variables cannot be changed. This makes it easy to understand about the object&apos;s state and prevents bugs that can arise from unexpected changes to the object.</p><p>However, if we need to change any one instance variable by keeping the other variable same we can do that using <code>with</code> method. </p><pre><code class="language-ruby">point2 = point.with(x: 10)
=&gt; #&lt;data Point x=10, y=4&gt;</code></pre><p>Since <code>Data</code> objects are immutable, <code>with</code> method creates a copy of the object to update the arguments. <br><sub>Note: If the <code>with</code> method is called with no arguments, the receiver is returned as-is and no new copy is created.</sub></p><p><code>Data.define</code> also accepts an optional block that can be used to define custom methods for the immutable object. These methods can provide additional functionality to the object without compromising its immutability.</p><pre><code class="language-ruby">class Point &lt; Data.define(:x, :y)
  def distance_from_origin
    Math.sqrt(x**2 + y**2)
  end
end

point = Point.new(3, 4)
=&gt; #&lt;data Point x=3, y=4&gt;

point.distance_from_origin
=&gt; 5.0
</code></pre><h3 id="why-not-struct">Why not Struct?</h3><p>While <code><a href="https://ruby-doc.org/3.2.1/Struct.html">Struct</a></code> can also be used to define objects, there are some reasons why <code>Data</code> might be a better choice in certain situations.</p><p>First, <code>Data</code> provides some safety checks that <code>Struct</code> does not. For example, <code>Data</code> prevents an object from being created with a missing argument, while <code>Struct</code> allows this. This can help to prevent bugs and improve code safety.</p><p>Here&apos;s an example:</p><pre><code class="language-ruby">Measure = Struct.new(:amount, :unit)

measure = Measure.new(30) # This works, but will fail with Data
=&gt; #&lt;struct Measure amount=30, unit=nil&gt;</code></pre><pre><code class="language-ruby">Point.new(x: 3) # Missing argument will raise error for Data
=&gt; `initialize&apos;: missing keyword: :y (ArgumentError)</code></pre><p>In this example, we&apos;ve defined a <code>Measure</code> struct that contains two instance variables: <code>amount</code> and <code>unit</code>. However, when we create a new instance of the object, we only provide one argument. This is allowed by <code>Struct</code>, but it can lead to bugs if the code assumes that all the instance variables are present.</p><p>Second, <code>Data</code> is a more explicit way of defining simple immutable objects. The <code>Data</code> class makes it clear that the object is intended to be immutable, while with <code>Struct</code> it&apos;s less clear.</p><h3 id="final-thoughts">Final Thoughts</h3><p><code>Data</code> is a powerful tool for defining simple immutable objects in Ruby 3.2. While it may not be suitable for all situations, its safety, performance, and clarity benefits make it a strong choice in many cases. By understanding how <code>Data</code> works and its tradeoffs compared to other techniques, such as <code>Struct</code>, you can make an informed decision about when to use it in your own code.</p><h3 id="references">References</h3><ol><li><a href="https://github.com/ruby/ruby/pull/6353">Add Data class implementation: Simple immutable value object PR#6353</a></li><li><a href="https://github.com/ruby/ruby/pull/6766">Add copy with changes functionality for Data objects PR#6766</a></li><li><a href="https://docs.ruby-lang.org/en/3.2/Data.html">https://docs.ruby-lang.org/en/3.2/Data.html</a></li><li><a href="https://bugs.ruby-lang.org/issues/16122">https://bugs.ruby-lang.org/issues/16122</a></li></ol>]]></content:encoded></item><item><title><![CDATA[Ruby 3.2.0 enhances Regexp performance and security with ReDoS protections]]></title><description><![CDATA[The release of Ruby 3.2.0 brings significant improvements to Regexp performance and security, including enhanced protection against ReDoS attacks.]]></description><link>https://blog.kiprosh.com/ruby-3-2-0-introduce/</link><guid isPermaLink="false">6379fd193cff3c20e60974d8</guid><category><![CDATA[ruby 3.2.0]]></category><category><![CDATA[performance]]></category><category><![CDATA[security]]></category><category><![CDATA[regex]]></category><category><![CDATA[ruby]]></category><dc:creator><![CDATA[Krishna Singh]]></dc:creator><pubDate>Wed, 01 Mar 2023 08:45:54 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="what-is-redos">What is ReDoS?</h2>
<p>Regular expression Denial of Service (ReDoS) is a security vulnerability that can occur in a regular expression (regex) when the regex is applied to a long string. This attack is designed to make a system or network unavailable to its intended users.</p>
<h3 id="an-example-occurrence-of-a-redos">An example occurrence of a ReDoS</h3>
<ol>
<li>
<p>Imagine that a website has a form that accepts user input and uses a regex to validate the input. The regex is designed to only allow alphanumeric characters in the input, so it looks like this: <code>/^[a-zA-Z0-9]+$/</code>.</p>
</li>
<li>
<p>An attacker could potentially craft a string of input that consists of a very long sequence of characters, such as this:<br>
<code>&apos;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&apos;</code>.</p>
</li>
<li>
<p>When this string is passed through the regex, it will take a very long time to validate, potentially causing the system to become unresponsive or crash. This would prevent legitimate users from accessing the website, effectively denying them service.</p>
</li>
<li>
<p>To prevent this attack, it is important to use regexes that are designed to be efficient and not susceptible to ReDoS attacks. This can involve using certain regex constructs and patterns that are known to be efficient, and avoiding certain constructs and patterns that can cause regexes to be slow or vulnerable to ReDoS attacks. It is also important to test regexes for efficiency and security before deploying them to the production environment.</p>
</li>
</ol>
<h2 id="how-to-prevent-redos-in-ruby-320">How to prevent ReDoS in Ruby &lt; 3.2.0 ?</h2>
<p>To prevent ReDoS attacks in a Ruby on Rails application, there are several steps that you can take:</p>
<ol>
<li>
<p>Avoid using the <code>match</code> method in your regexes, as this method can be slow and can lead to ReDoS attacks. Instead, use the <code>scan</code> or <code>grep</code> methods, which are faster and more efficient.</p>
</li>
<li>
<p>Avoid using regexes with nested quantifiers such as <code>(a+)*</code> or with unbounded repetitions such as <code>a*</code> or <code>a+</code> in your Rails application. These types of regexes can be very slow and can lead to ReDoS attacks.</p>
</li>
<li>
<p>Use the <code>/o</code> option in your regexes to prevent them from being compiled multiple times. This can improve the efficiency of your regexes and can prevent ReDoS attacks.</p>
</li>
<li>
<p>Test your regexes for efficiency and security before deploying them in a production environment. This can help you identify any potential ReDoS vulnerabilities and can allow you to fix them before they can be exploited by an attacker.</p>
</li>
</ol>
<h2 id="regexp-improvements-introduced-in-ruby-320-to-prevent-redos">Regexp improvements introduced in Ruby 3.2.0 to prevent ReDoS.</h2>
<p>Ruby 3.2.0 introduced two improvements that significantly mitigate ReDoS.</p>
<h4 id="improved-regexp-matching-algorithm">Improved Regexp matching algorithm</h4>
<p>Since Ruby 3.2, the matching algorithm for Regexp has been significantly enhanced by using a memoization technique:</p>
<ol>
<li>
<p>This technique improves the performance of regexp matching by allowing most regexp matches to be completed in linear time.</p>
</li>
<li>
<p>This means that the time it takes to complete a regexp match will be directly proportional to the length of the input string, and will not increase exponentially as the input string gets longer.</p>
</li>
<li>
<p>This can help prevent ReDoS attacks, which are a type of security vulnerability that can occur when a regexp is applied to a long string of input.</p>
</li>
<li>
<p>Also, this optimization may consume memory proportional to the input length for each match. This means that the amount of memory used by the optimization will increase as the length of the input string increases.</p>
</li>
<li>
<p>This should not cause any practical problems because the memory allocation is usually delayed, and a normal regexp match should consume at most 10 times as much memory as the input length.</p>
</li>
</ol>
<p><strong>Example:-</strong></p>
<ul>
<li><strong>With <code>3.1.0</code></strong></li>
</ul>
<pre><code class="language-ruby">  :001 &gt; require &apos;benchmark&apos;
  =&gt; true
  :002 &gt; Benchmark.realtime { /^a*b?a*$/ =~ &quot;a&quot; * 50000 + &quot;x&quot; }
  =&gt; 24.171763999998802
</code></pre>
<ul>
<li><strong>With <code>3.2.0</code></strong></li>
</ul>
<pre><code class="language-ruby">  :001 &gt; require &apos;benchmark&apos;
  =&gt; true
  :002 &gt; Benchmark.realtime { /^a*b?a*$/ =~ &quot;a&quot; * 50000 + &quot;x&quot; }
  =&gt; 0.007867999986046925
</code></pre>
<h4 id="preventing-redos-attacks-with-regexp-timeouts">Preventing ReDoS attacks with regexp timeouts</h4>
<p>This allows you to specify a timeout for regexp matching. Two different APIs that can be used to set a timeout for regexp matching are:</p>
<ol>
<li>
<h4 id="regexptimeout"><code>Regexp.timeout=</code></h4>
<p>This is the process-global configuration of timeout for regexp matching. It allows you to specify a timeout that will apply to all regexp matches in your Ruby application. For example, you can use it like this:</p>
<pre><code class="language-ruby">  # Set a timeout of 1 second for regexp matching
  Regexp.timeout = 1.0

  regexp = Regexp.new(&quot;^[a-zA-Z0-9]+$&quot;)

  # Perform a regexp match
  regexp.scan(string)
</code></pre>
<p>In this example, the <code>Regexp.timeout</code> global configuration is set to 1 second. This means that any regexp match performed in the application will have a timeout of 1 second. If the match takes longer than 1 second to complete, it will raise a <code>Regexp::TimeoutError</code>.</p>
</li>
<li>
<h4 id="timeout-keyword-of-regexpnew"><code>timeout</code> keyword of <code>Regexp.new</code></h4>
<p>This API allows you to specify a timeout for a specific regexp object. This is useful when you want to use different timeout settings for different regexps in your application. For example, you can use it like this:</p>
<pre><code class="language-ruby">  # Create a regexp with a timeout of 1 second
  regexp = Regexp.new(&quot;^[a-zA-Z0-9]+$&quot;, timeout: 1.0)

  # Perform a regexp match
  regexp.scan(string)
</code></pre>
<p>In this example, the <code>timeout</code> keyword is used with the <code>Regexp.new</code> method to specify a timeout of 1 second for the regexp object. This means that any match performed with this regexp object will have a timeout of 1 second. If the match takes longer than 1 second to complete, it will raise a <code>Regexp::TimeoutError</code>.</p>
</li>
</ol>
<hr>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.ruby-lang.org/en/news/2022/12/25/ruby-3-2-0-released/">Ruby 3.2.0 Released</a></li>
<li><a href="https://blog.saeloun.com/2022/08/09/ruby-introduces-regexp-timeout.html">Ruby introduces Regexp.timeout</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Improvements to in_order_of active record query method in Rails 7.1]]></title><description><![CDATA[This article explores how the in_order_of method has been improved in Rails 7.1. This method allows sorting records in a customized order.]]></description><link>https://blog.kiprosh.com/improvements-to-in_order_of-active-record-query-method-in-rails-7-1/</link><guid isPermaLink="false">63ac6c2966f7f23223061985</guid><category><![CDATA[rails_7]]></category><category><![CDATA[rails_7.1]]></category><category><![CDATA[in_order_of]]></category><category><![CDATA[active_record]]></category><dc:creator><![CDATA[Supriya Laxman Medankar]]></dc:creator><pubDate>Thu, 16 Feb 2023 10:12:10 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1588348442528-85c6fa3b0440?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG9yZGVyfGVufDB8fHx8MTY3NDUzOTQzNg&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1588348442528-85c6fa3b0440?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG9yZGVyfGVufDB8fHx8MTY3NDUzOTQzNg&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Improvements to in_order_of active record query method in Rails 7.1"><p><code>Rails 7</code> has introduced the <code>in_order_of</code> method which allows to sort records in a specific order. There is a detailed explanation of it in the article <strong><a href="https://blog.kiprosh.com/rails-7-in_order_of-method-for-activerecord-and-enumerable/">Rails 7 adds in_order_of for ActiveRecord::QueryMethods and Enumerable</a></strong>. This is a follow-up article to that one, in which we will explore how the <code>in_order_of</code> method has been improved in <strong>Rails 7.1</strong>.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://blog.kiprosh.com/rails-7-in_order_of-method-for-activerecord-and-enumerable/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Rails 7 adds in_order_of for ActiveRecord::QueryMethods and Enumerable</div><div class="kg-bookmark-description">Rails 7 has added in_order_of method for ActiveRecord::QueryMethods and Enumerable to retrieve the data in an explicit order.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://blog.kiprosh.com/favicon.png" alt="Improvements to in_order_of active record query method in Rails 7.1"><span class="kg-bookmark-author">Kiprosh Blogs</span><span class="kg-bookmark-publisher">Supriya Laxman Medankar</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://blog.kiprosh.com/content/images/2022/05/rails7-in_order_of.png.png" alt="Improvements to in_order_of active record query method in Rails 7.1"></div></a></figure><!--kg-card-begin: markdown--><p><strong>Allows to order by string column name</strong></p>
<p>In <strong>Rails 7</strong>, if a column name is passed to the <code>in_order_of</code> method as a string, it raises the following error:</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.0.3)
3.0.0 :001 &gt; CustomerOrder.in_order_of(&apos;name&apos;, [&apos;Shirt&apos;, &apos;Jeans&apos;])
/usr/share/rvm/gems/ruby-3.0.0/gems/activerecord-7.0.3/lib/active_record/relation/query_methods.rb:459:in `in_order_of&apos;: undefined method `in&apos; for &quot;name&quot;:String (NoMethodError)                                             
Did you mean?  in?           
</code></pre>
<p>It has been fixed in <a href="https://github.com/rails/rails/pull/45971">PR#45971</a>. From <strong>Rails 7.1</strong> onwards, we are allowed to pass the column name as a string value to the <code>in_order_of</code> method.</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.1.0.alpha)
3.0.3 :001 &gt; CustomerOrder.in_order_of(&quot;name&quot;, [&apos;Shirt&apos;, &apos;Jeans&apos;])
  CustomerOrder Load (0.5ms)  SELECT &quot;customer_orders&quot;.* FROM &quot;customer_orders&quot; WHERE &quot;customer_orders&quot;.&quot;name&quot; IN (&apos;Shirt&apos;, &apos;Jeans&apos;) /* loading for pp */ ORDER BY CASE WHEN &quot;customer_orders&quot;.&quot;name&quot; = &apos;Shirt&apos; THEN 1 WHEN &quot;customer_orders&quot;.&quot;name&quot; = &apos;Jeans&apos; THEN 2 END ASC LIMIT ?  [[&quot;LIMIT&quot;, 11]]                                                            
 =&gt;                                                                   
[#&lt;CustomerOrder:0x000055cb391a3600                                   
  id: 1,
  name: &quot;Shirt&quot;,
  order_status: &quot;Completed&quot;,
  created_at: Mon, 23 Jan 2023 15:42:08.473523000 UTC +00:00,
  updated_at: Mon, 23 Jan 2023 15:42:08.473523000 UTC +00:00&gt;,
 #&lt;CustomerOrder:0x000055cb391a3538
  id: 2,
  name: &quot;Jeans&quot;,
  order_status: nil,
  created_at: Mon, 23 Jan 2023 15:43:03.940857000 UTC +00:00,
  updated_at: Mon, 23 Jan 2023 15:43:03.940857000 UTC +00:00&gt;]    
</code></pre>
<p><strong>Works with nil values</strong></p>
<p>In <strong>Rails 7</strong>, if <code>nil</code> is passed to the <code>in_order_of</code> method, the following SQL is generated:</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.0.3)
3.0.0 :001 &gt; CustomerOrder.in_order_of(:order_status, [nil, &apos;Completed&apos;]).to_sql
 =&gt; &quot;SELECT \&quot;customer_orders\&quot;.* FROM \&quot;customer_orders\&quot; WHERE \&quot;customer_orders\&quot;.\&quot;order_status\&quot; IN (NULL, &apos;Completed&apos;) ORDER BY CASE \&quot;customer_orders\&quot;.\&quot;order_status\&quot; WHEN NULL THEN 1 WHEN &apos;Completed&apos; THEN 2 WHEN ELSE 3 END ASC&quot; 
</code></pre>
<p>As we can see from the example above, the <code>CASE</code> statement adds a check for <code>WHEN NULL THEN</code>, but <code>NULL != NULL</code> in SQL. As a result, when ordering, records with <code>NULL</code> values are ignored, and those records are excluded from the final result.</p>
<p>In <strong>Rails 7.1</strong>, The <a href="https://github.com/rails/rails/pull/45670">PR#45670</a> has added a fix to generate SQL <code>WHEN \&quot;customer_orders\&quot;.\&quot;order_status\&quot; IS NULL</code> for <code>nil</code> values, which sort records correctly and return records with <code>nil</code> value in the result.</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.1.0.alpha)
3.0.3 :001 &gt;  CustomerOrder.in_order_of(:order_status, [nil, &apos;Completed&apos;])
  CustomerOrder Load (0.3ms)  SELECT &quot;customer_orders&quot;.* FROM &quot;customer_orders&quot; WHERE (&quot;customer_orders&quot;.&quot;order_status&quot; IN (&apos;Completed&apos;) OR &quot;customer_orders&quot;.&quot;order_status&quot; IS NULL) /* loading for pp */ ORDER BY CASE WHEN &quot;customer_orders&quot;.&quot;order_status&quot; IS NULL THEN 1 WHEN &quot;customer_orders&quot;.&quot;order_status&quot; = &apos;Completed&apos; THEN 2 END ASC LIMIT ?  [[&quot;LIMIT&quot;, 11]]              
 =&gt;                                                                                          
[#&lt;CustomerOrder:0x000055cb3822b308                                                          
  id: 2,                                                                                     
  name: &quot;Jeans&quot;,                                                                              
  order_status: nil,                                                                         
  created_at: Mon, 23 Jan 2023 15:43:03.940857000 UTC +00:00,                                
  updated_at: Mon, 23 Jan 2023 15:43:03.940857000 UTC +00:00&gt;,                               
 #&lt;CustomerOrder:0x000055cb3822b240                                                          
  id: 1,                                                                                     
  name: &quot;Shirt&quot;,                                                                             
  order_status: &quot;Completed&quot;,                                                                 
  created_at: Mon, 23 Jan 2023 15:42:08.473523000 UTC +00:00,                                
  updated_at: Mon, 23 Jan 2023 15:42:08.473523000 UTC +00:00&gt;]
</code></pre>
<p><strong>Using ORDER BY CASE for all</strong></p>
<p>In <strong>Rails 7</strong>, <code>in_order_of</code> uses the special order field generation, i.e. <code>ORDER BY FIELD</code> for the <code>MySQL</code> adapter. This does not add any performance improvement other than a simplified query, so it has been replaced by <code>ORDER BY CASE</code>.</p>
<p>From <strong>Rails 7.1</strong> onwards, all database adapters will now use <code>ORDER BY CASE</code>. The <a href="https://github.com/rails/rails/pull/45670">PR#45670</a> has added this fix, the same PR which has added fix for <code>in_order_of</code> to work with <code>nils</code>.</p>
<h3 id="references">References:</h3>
<ol>
<li><a href="https://github.com/rails/rails/pull/45971">improve &quot;in_order_of&quot; to allow string column name</a></li>
<li><a href="https://github.com/rails/rails/pull/45670">Fix ActiveRecord::QueryMethods#in_order_of to work with nils</a></li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Rails 7: ActiveStorage::Streaming improves streaming from the controller]]></title><description><![CDATA[Once we include ActiveStorage::Streaming in a controller, we get access to the #send_blob_stream method which takes care of everything, from writing the headers to streaming the downloaded data chunks to the client to closing the stream after it is completed.]]></description><link>https://blog.kiprosh.com/rails-7-active-storage-streaming/</link><guid isPermaLink="false">637344313cff3c20e609742c</guid><category><![CDATA[rails_7]]></category><category><![CDATA[active-storage]]></category><category><![CDATA[ruby-on-rails]]></category><category><![CDATA[streaming]]></category><dc:creator><![CDATA[Atique Akhtar]]></dc:creator><pubDate>Wed, 18 Jan 2023 07:30:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1616469829526-7057a1427626?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fHZpZGVvJTIwc3RyZWFtaW5nfGVufDB8fHx8MTY2ODQ5ODQ0Mw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1616469829526-7057a1427626?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDh8fHZpZGVvJTIwc3RyZWFtaW5nfGVufDB8fHx8MTY2ODQ5ODQ0Mw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Rails 7: ActiveStorage::Streaming improves streaming from the controller"><p>In the era of content consumption where streaming platforms like YouTube and Netflix have millions of concurrent users, no one wants to wait more than a minute to consume anything, it must be available instantly!</p>
<p>Streaming becomes a need in this situation. While downloading requires waiting until the entire file has been downloaded on your computer, streaming allows you to view downloaded portions of the material on the fly. This significantly reduces the waiting time.</p>
<p>Let&apos;s see with the help of a small example how streaming was handled before Rails 7 and what changed after Rails 7.</p>
<h2 id="with-rails-7">With Rails &lt; 7</h2>
<p>Suppose we need to write a controller action that streams data chunks to the client in real time as they are getting downloaded.</p>
<p>Before Rails 7, we were required to include the <code>ActionController::Live</code> module for live streaming to work. By <em>mixin</em> the <code>ActionController::Live</code> module in our application&apos;s controller, we enable all actions in that controller to stream data to the client as it is written in real-time.</p>
<p><em>Note: The response headers must be explicitly specified in all actions of the controller. This enables the browser to recognize the type of file it should display.</em></p>
<pre><code class="language-ruby">class VideoController &lt; ApplicationController
  include ActionController::Live

  def show
    response.headers[&quot;Content-Type&quot;] = @video.content_type
    response.headers[&quot;Content-Disposition&quot;] = &quot;inline; #{@video.filename.parameters}&quot;

    # Streaming downloaded chunks of data to the client as the video downloads
    @video.download do |chunk|
      response.stream.write(chunk)
    end

    response.stream.close
  end
end

</code></pre>
<p><code>Content-Disposition</code> is a response header indicating if the content is expected to be displayed inline in the browser, that is, as a Web page or as part of a Web page, or as an attachment, that is downloaded and saved locally.</p>
<h3 id="few-caveats-with-rails-7">Few caveats with Rails &lt; 7:</h3>
<ol>
<li>You need to write headers for the actions before a response has been committed (calling <em>write</em> or <em>close</em> on the response stream will cause the response object to be committed). Make sure all headers are set before calling write or close on your stream.</li>
<li>You must close the stream when it&apos;s finished, otherwise, the socket will remain open forever.</li>
</ol>
<h2 id="with-rails-7">With Rails &gt;= 7</h2>
<p><code>ActiveStorage::Streaming</code> module is introduced in <a href="https://github.com/rails/rails/pull/41440">this PR</a>, which supports native file streaming from the controller. Once we include <code>ActiveStorage::Streaming</code> in a controller, we get access to the <code>#send_blob_stream</code> method which takes care of everything, from writing the headers to streaming the downloaded data chunks to the client to closing the stream after it is completed.</p>
<p>Now we don&apos;t need to re-write response headers and close the stream before and after every streaming action.</p>
<pre><code class="language-ruby">class VideoController &lt; ApplicationController
  include ActiveStorage::Streaming

  def show
    http_cache_forever(public: true) do
      send_blob_stream @video, disposition: params[:disposition]
    end
  end
end
</code></pre>
<p>The method <code>http_cache_forever</code> allows us to set response headers to tell the browser that the response has not been modified. It means that on the first hit, we serve the request normally but, then on each subsequent request, the cache is revalidated and a <em>304 Not Modified</em> response is sent to the browser. This saves us from setting the same response header on every request.</p>
<pre><code class="language-ruby"># First request:

Processing by VideoController#show as HTML
  Rendered videos/show.html.erb within layouts/application (1.8ms)
Completed 200 OK in 198ms (Views: 256.7ms | ActiveRecord: 0.0ms)

# Consecutive requests for the same page:

Processing by VideoController#show as HTML
Completed 304 Not Modified in 3ms (ActiveRecord: 0.0ms)
</code></pre>
<p><code>Rails</code> has started to expand its streaming capabilities, and we may see more updates in the coming months. Checkout most recent updates in the pull requests below:</p>
<ol>
<li><a href="https://github.com/rails/rails/pull/41440">Extract ActiveStorage::Streaming so your own controllers can use it</a></li>
<li><a href="https://github.com/rails/rails/pull/44244">Don&apos;t stream redirect controller responses</a></li>
<li><a href="https://github.com/rails/rails/blob/8865834cbd85d99bf19c125c993d060f37b909d5/activestorage/app/controllers/concerns/active_storage/streaming.rb">Rails ActiveStorage::Streaming module</a></li>
</ol>
<h2 id="references">References:</h2>
<ul>
<li>Rails PR discussion for adding support to build a controller that streams from active storage - <a href="https://github.com/rails/rails/pull/41440">Extract ActiveStorage::Streaming so your own controllers can use it</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Rails 7.1 - Raises on assignment to readonly attributes]]></title><description><![CDATA[Rails 7.1 adds raise_on_assign_to_attr_readonly config to raise ActiveRecord::ReadonlyAttributeError error on attr_readonly attributes assignment]]></description><link>https://blog.kiprosh.com/rails-7-1-raises-on-assignment-to-readonly-attributes/</link><guid isPermaLink="false">6382e0d23cff3c20e609777c</guid><category><![CDATA[rails_7]]></category><category><![CDATA[rails 7.1]]></category><category><![CDATA[active-record]]></category><dc:creator><![CDATA[Sampat Badhe]]></dc:creator><pubDate>Thu, 12 Jan 2023 07:30:00 GMT</pubDate><media:content url="https://blog.kiprosh.com/content/images/2023/01/carbon--12-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://blog.kiprosh.com/content/images/2023/01/carbon--12-.png" alt="Rails 7.1 - Raises on assignment to readonly attributes"><p>Rails 7.1 adds the <code>raise_on_assign_to_attr_readonly</code> config to <code>config.active_record</code> that is used to raise <code>ActiveRecord::ReadonlyAttributeError</code> error on assignment to <code>attr_readonly</code> attributes. The previous behavior would allow assignment but silently not persist changes to the database.</p>
<h3 id="configuring-configactiverecordraiseonassigntoattrreadonly">Configuring config.active_record.raise_on_assign_to_attr_readonly</h3>
<p>In a newly created Rails 7.1 application, <code>config.load_defaults 7.1</code> is set by default in <code>application.rb</code>. The default value of <code>raise_on_assign_to_attr_readonly</code> for <code>config.load_defaults 7.1</code> is <code>true</code> and for <code>config.load_defaults &lt; 7.1</code> it is <code>false</code>. To opt-in:</p>
<pre><code class="language-ruby"># config/environments/application.rb

config.active_record.raise_on_assign_to_attr_readonly = true
</code></pre>
<p>Let&apos;s take an example of the following Post model structure:</p>
<pre><code class="language-ruby"># app/models/post.rb

class Post &lt; ActiveRecord::Base
  attr_readonly :title
end
</code></pre>
<ul>
<li>
<p><strong>When <code>config.active_record.raise_on_assign_to_attr_readonly = true</code></strong></p>
<pre><code class="language-ruby">post = Post.create!(title: &quot;Introducing Ruby on Rails!&quot;)
post.title = &quot;a different title&quot; # raises ActiveRecord::ReadonlyAttributeError
post.update(title: &quot;a different title&quot;) # raises ActiveRecord::ReadonlyAttributeError
</code></pre>
</li>
<li>
<p><strong>When <code>config.active_record.raise_on_assign_to_attr_readonly = false</code></strong></p>
<pre><code class="language-ruby">post = Post.create!(title: &quot;Introducing Ruby on Rails!&quot;)
post.title = &quot;a different title&quot; # does not raise any error
post.update(title: &quot;a different title&quot;) # change to title will be ignored
</code></pre>
</li>
</ul>
<h3 id="references">References</h3>
<ul>
<li><a href="https://github.com/rails/rails/pull/46105">Rails PR#46105</a> &amp; <a href="https://github.com/rails/rails/pull/46602">Rails PR#46602</a> - Raise on assignment to readonly attributes</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[A quick dive into the new query_constraints config introduced in Rails 7.1]]></title><description><![CDATA[The query_constraints config introduced in Rails 7.1 is helpful for pre-setting the DB query constraints on the ActiveRecord models.]]></description><link>https://blog.kiprosh.com/rails-7-adds-column-constraints-for-an-activerecord-base-object/</link><guid isPermaLink="false">6367cfab3cff3c20e6096ea9</guid><category><![CDATA[rails_7]]></category><category><![CDATA[query-constraints]]></category><category><![CDATA[active-record]]></category><category><![CDATA[rails 7.1]]></category><dc:creator><![CDATA[Karan Valecha 👨🏻‍💻]]></dc:creator><pubDate>Wed, 11 Jan 2023 10:17:36 GMT</pubDate><content:encoded><![CDATA[<p><strong>ActiveRecord</strong> in its current state is designed to work with the primary key column (the default is the <code>id</code> column). Hence, if you notice the <code>update</code>, <code>destroy</code>, and <code>reload</code> actions on an <strong>ActiveRecord::Base</strong> object, it fetches the data based on the <code>id</code> column, and would not have the flexibility to choose any different set of constraints while performing those 3 actions.</p><p>Here&apos;s an example of <code>reload</code> action on the <code>Post</code> model instance(<code>post.reload</code>).</p><pre><code class="language-ruby">Post Load (1.3ms) &#xA0;SELECT &quot;posts&quot;.* FROM &quot;posts&quot; WHERE &quot;posts&quot;.&quot;id&quot; = $1 LIMIT $2 &#xA0;[[&quot;id&quot;, 1], [&quot;LIMIT&quot;, 1]]
</code></pre><p>If the app we have set up has good composite indexes, before the ActiveRecord query_constraints config (introduced in Rails 7.1), the ActiveRecord could not apply such constraints by default &amp; utilize the composite indexes, especially with the <code>update</code>, <code>destroy</code>, and <code>reload</code> actions.</p><p>The new ActiveRecord config <code>query_constraints</code> introduced in <a href="https://github.com/rails/rails/pull/46331/files#top">PR</a> changes this behavior and allows us to specify specific columns whenever fetching/performing actions on an ActiveRecord::Base object, we&apos;ll learn how.</p><hr><p>Let&apos;s understand the need of <code>query_constraints</code> with a relatively simple example of an eCommerce SaaS model of <code>Store &amp; Product</code>:</p><pre><code class="language-ruby"># == Schema Information
#
# Table name: products
#
#  id          :integer(4)      not null, primary key
#  store_id    :integer(4)
#  category_id :integer(4)
#  title       :string(255)

# Indexes
# :id -&gt; Unique, Primary Key
# [:store_id, :category_id] -&gt; Composite Index

class Product &lt; ActiveRecord::Base
&#xA0;belongs_to :store
end

product = Product.first
# =&gt; SELECT &quot;products&quot;.&quot;*&quot; from &quot;products&quot; LIMIT 1

product.update(title: &quot;Lord of the Rings&quot;)
# =&gt; UPDATE &quot;products&quot; SET &quot;title&quot; = &apos;Lord of the Rings&apos; WHERE &quot;products&quot;.&quot;id&quot; = 1

</code></pre><hr><p>In the above example, we see that the <code>id</code> column was used in the where clause, and we were not able to properly utilize the composite indexes <code>store_id</code> and <code>category_id</code>.</p><p>Now let&apos;s see the behavior after adding the <code>query_constraints</code> to the same setup:</p><pre><code class="language-ruby"># == Schema Information
#
# Table name: products
#
#  id          :integer(4)      not null, primary key
#  store_id    :integer(4)
#  category_id :integer(4)
#  title       :string(255)

# Indexes
# :id -&gt; Unique, Primary Key
# [:store_id, :category_id] -&gt; Composite Index

class Product &lt; ActiveRecord::Base
&#xA0;query_constraints :store_id, :category_id, :id
&#xA0;belongs_to :store
end

product = Product.first
# =&gt; SELECT &quot;products&quot;.&quot;*&quot; from &quot;products&quot; LIMIT 1

product.update(title: &quot;Lord of the Rings&quot;)
# =&gt; UPDATE &quot;products&quot; SET &quot;title&quot; = &apos;Lord of the Rings&apos;\
# WHERE &quot;products&quot;.&quot;store_id&quot; = 1 AND &quot;products&quot;.&quot;category_id&quot;=&apos;11&apos; AND &quot;products&quot;.&quot;id&quot; = 1

</code></pre><p>In this example, with <code>query_constraints</code> configured, the query automatically and effectively uses our composite indexes <code>store_id</code> and <code>category_id</code>.</p><p>The query_constraints might be vital for the apps that are designed to work with composite primary keys, for which a prime example would be a Multi-Tenant sharded database design.</p><hr><h4 id="what-a-multi-tenant-sharded-database-is">What a Multi-Tenant sharded database is?</h4><p>A database can be designed depending on performance, scaling, and maintenance requirements.</p><p>In a multi-tenant sharded database pattern, the table schemas inside each database have a <strong>tenant key</strong> in the primary key of tables that stores the tenant data. This &quot;tenant key&quot; enables each individual database to have 1 or many tenants.</p><p>A tenant&apos;s data can be distributed across multiple databases or shards, where all the data for a single tenant is contained in one shard.</p><figure class="kg-card kg-image-card"><img src="https://i.stack.imgur.com/o2HxS.jpg" class="kg-image" alt loading="lazy"></figure><hr><p>Coming back to our above example of the eCommerce SaaS model of <code>Store &amp; Product</code>, consider if we decided to have Database sharding on the <code>store_id</code> column, the default constraints become a must-have.</p><p><strong>To conclude:</strong></p><blockquote>With the changes like <a href="https://github.com/rails/rails/pull/46331/files#top">Allow specifying columns to use in ActiveRecord::Base object queries</a>, the Rails framework is being prepared to provide generic solutions to the Multi-Tenant Sharded database implementations. With this, the conventional dependency of having an <code>id</code> column for an ActiveRecord::Base object will go away and provide us the ability to tread the composite columns as the primary key(like in our example it was <code>[:store_id, :category_id]</code>).</blockquote>]]></content:encoded></item><item><title><![CDATA[Rails 7.1 supports infinite ranges for Active Record Validators]]></title><description><![CDATA[Rails 7.1 supports infinite ranges that can be used with Active Record length and inclusivity validators.]]></description><link>https://blog.kiprosh.com/rails-7-supports-infinite-ranges-for-active-record-validators/</link><guid isPermaLink="false">63898dd23cff3c20e6097973</guid><category><![CDATA[rails_7]]></category><category><![CDATA[rails]]></category><category><![CDATA[rails 7.1]]></category><category><![CDATA[active-record]]></category><category><![CDATA[ruby]]></category><category><![CDATA[Activerecord Validation]]></category><category><![CDATA[infinte-ranges]]></category><dc:creator><![CDATA[Athira Kadampatta]]></dc:creator><pubDate>Wed, 04 Jan 2023 07:30:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1638414154639-0fbc5bceb80f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGluZmluaXR5fGVufDB8fHx8MTY3MjAzMzg2Nw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1638414154639-0fbc5bceb80f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGluZmluaXR5fGVufDB8fHx8MTY3MjAzMzg2Nw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Rails 7.1 supports infinite ranges for Active Record Validators"><p></p><!--kg-card-begin: markdown--><p>Ruby ranges are very handy and efficient when you want to work with a sequence of numbers or characters bound by start and end values. But we could come across situations where we want ranges that either have no end value or start value. Ruby 2.6 introduced the concept of an endless range and later Ruby 2.7 brought in the beginless range.</p>
<h3 id="infinite-ranges-in-ruby">Infinite Ranges in Ruby</h3>
<p>As mentioned in the <a href="https://ruby-doc.org/2.7.7/Range.html#class-Range-label-Beginless-2FEndless+Ranges">official Ruby docs</a>, a <code>beginless range</code> and <code>endless range</code> represents a semi-infinite range.<br>
A beginless range would look like <code>(..3)</code> Or <code>(...3)</code> while endless range looks like <code>(3..)</code> Or <code>(3...)</code> and effectively defines the maximum or minimum boundary of the range.</p>
<h3 id="infinite-ranges-in-rails">Infinite Ranges in Rails</h3>
<p>Infinite ranges are supported in Rails while fetching records from the database as seen below:</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.1.0.alpha)
irb(main):013:0&gt; Event.where(date:  (..Time.now))
  Event Load (0.5ms)  SELECT &quot;events&quot;.* FROM &quot;events&quot; WHERE &quot;events&quot;.&quot;date&quot; &lt;= ? /* loading for pp */ LIMIT ?  [[&quot;date&quot;, &quot;2022-12-11 12:56:31.866828&quot;], [&quot;LIMIT&quot;, 11]]
=&gt;
[#&lt;Event:0x000055a3f20b22f0
  id: 1,                                                              
  date: Sun, 11 Dec 2022 12:32:57.255815000 UTC +00:00,               
  name: &quot;The December Concert&quot;,                                       
  description: &quot;The ultimate concert to end the year&quot;,                
  created_at: Sun, 11 Dec 2022 12:32:57.256643000 UTC +00:00,         
  updated_at: Sun, 11 Dec 2022 12:32:57.256643000 UTC +00:00&gt;,
 #&lt;Event:0x000055a3f20b2188
  id: 5,
  date: Mon, 31 Oct 2022 18:30:00.000000000 UTC +00:00,
  name: &quot;The Halloween Festival&quot;,
  description: &quot;The eve of All Saints Day&quot;,
  created_at: Sun, 11 Dec 2022 12:55:33.831731000 UTC +00:00,
  updated_at: Sun, 11 Dec 2022 12:56:21.981937000 UTC +00:00&gt;]
</code></pre>
<p>The above query fetched all the past event records, i.e the ones with the <code>date</code> value less than the current time.</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.1.0.alpha)
irb(main):014:0&gt; Event.where(date:  (Time.now..))
  Event Load (0.2ms)  SELECT &quot;events&quot;.* FROM &quot;events&quot; WHERE &quot;events&quot;.&quot;date&quot; &gt;= ? /* loading for pp */ LIMIT ?  [[&quot;date&quot;, &quot;2022-12-11 12:56:43.438138&quot;], [&quot;LIMIT&quot;, 11]]
=&gt;
[#&lt;Event:0x000055a3f26616b0
  id: 2,
  date: Sat, 31 Dec 2022 18:30:00.000000000 UTC +00:00,
  name: &quot;The New Year Event&quot;,
  description: &quot;The first event of the year&quot;,
  created_at: Sun, 11 Dec 2022 12:34:23.292070000 UTC +00:00,
  updated_at: Sun, 11 Dec 2022 12:34:23.292070000 UTC +00:00&gt;,
 #&lt;Event:0x000055a3f26615e8
  id: 3,
  date: Thu, 12 Jan 2023 18:30:00.000000000 UTC +00:00,
  name: &quot;The Lohri Festival&quot;,
  description: &quot;The celebration of harvest festival&quot;,
  created_at: Sun, 11 Dec 2022 12:36:13.234757000 UTC +00:00,
  updated_at: Sun, 11 Dec 2022 12:37:13.423191000 UTC +00:00&gt;]
</code></pre>
<p>The above query fetched all the future event records, i.e the ones with the <code>date</code> value greater than the current time.</p>
<p>In addition to using ranges for fetching records, we generally use them in Rails with the Active Record length and inclusion validators.</p>
<h4 id="with-rails-710-alpha">With Rails &lt; 7.1.0-alpha</h4>
<p>If we use infinite range in a validator like:</p>
<pre><code class="language-ruby">class Event &lt; ApplicationRecord
  ...
  validates_length_of :name, in: (..30)
  ...
end
</code></pre>
<p>it throws an error as follows:</p>
<pre><code class="language-ruby">Loading development environment (Rails 7.0.1)
irb(main):001:0&gt; Event
Traceback (most recent call last):
        3: from (irb):1
        2: from app/models/event.rb:4:in `&lt;main&gt;&apos;
        1: from app/models/event.rb:7:in `&lt;class:Event&gt;&apos;
RangeError (cannot get the minimum of beginless range)
</code></pre>
<p>The only way to validate is by passing <code>maximum</code> or <code>minimum</code> value to the <code>validates</code> method as follows:</p>
<pre><code class="language-ruby">validates_length_of :name, maximum: 30, allow_blank: true
</code></pre>
<h4 id="with-rails-710-alpha">With Rails &gt;= 7.1.0-alpha</h4>
<p>With the two PRs that were merged recently - <a href="https://github.com/rails/rails/pull/45123">PR#45123</a> and <a href="https://github.com/rails/rails/pull/45138">PR#45138</a>, it is possible to pass beginless/endless ranges to the length and inclusivity validators with the <code>in/within</code> options.</p>
<pre><code class="language-ruby">  # Allows date to be either today&apos;s or any future date but not a past date
  validates_inclusion_of :date, in: -&gt; { (Date.today..) } 
</code></pre>
<pre><code class="language-ruby">  # Allows name to be nil or of a max length of 30 characters
  validates_length_of :name, in: (..30)

  # Mandates description to be of minimum 10 characters with no max limit
  validates_length_of :description, in: (10..)
</code></pre>
<p><em>Please note: These changes are available in the alpha phase of Rails 7.1.0 at the time of writing this article. To get the 7.1.0-alpha version, you have to specify the rails main branch in Gemfile -</em></p>
<pre><code class="language-ruby">    # Gemfile
    gem &apos;rails&apos;, github: &apos;rails/rails&apos;, branch: &apos;main&apos;
</code></pre>
<h3 id="references">References</h3>
<ul>
<li><a href="https://github.com/rails/rails/pull/45138">Support infinite ranges for LengthValidators :in/:within options</a></li>
<li><a href="https://github.com/rails/rails/pull/45123">Add beginless range support to clusivity</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Broadcasting your turbo streams as you want them in Rails 7]]></title><description><![CDATA[A blog about broadcasting turbo streams in Hotwire and solution to some of the common scenarios and issues faced while using them. ]]></description><link>https://blog.kiprosh.com/broadcasting-your-turbo-streams-as-you-want-them-in-rails-7/</link><guid isPermaLink="false">635e96193cff3c20e6096aac</guid><category><![CDATA[rails_7]]></category><category><![CDATA[hotwire]]></category><category><![CDATA[rails]]></category><category><![CDATA[turbo_stream]]></category><dc:creator><![CDATA[Imanpal Singh]]></dc:creator><pubDate>Tue, 27 Dec 2022 07:31:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1581092915436-b3f79d8267f8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDUzfHxicm9hZGNhc3R8ZW58MHx8fHwxNjY4NDA5MjA4&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1581092915436-b3f79d8267f8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDUzfHxicm9hZGNhc3R8ZW58MHx8fHwxNjY4NDA5MjA4&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Broadcasting your turbo streams as you want them in Rails 7"><p>Hotwire has been around for a while now. If you got a chance to create an app powered by it yet, you must have realized how easy it is to create apps that feel like single-page applications. In this blog, we will see the lesser-discovered part of the ecosystem - broadcasting.</p><h2 id="turbo-streams">Turbo streams</h2><p>Think of turbo streams as a set of changes to perform on your UI. You send these changes back to the UI as a response to an event, form submission, or whenever you feel like it. If you are new to this, I recommend going through the <a href="https://blog.kiprosh.com/turbo-the-heart-of-hotwire/">Turbo - The heart of Hotwire</a> first.</p><h2 id="broadcasting">Broadcasting?</h2><p>If you are creating an application where you want changes in the UI to be reflected not just for the instance that requested it but for others (who&apos;re viewing the same page) too, then broadcasting is what you need.</p><p>Let&apos;s take a simple example of a notifications feature.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.kiprosh.com/content/images/2022/12/Untitled-2022-12-11-2045.png" class="kg-image" alt="Broadcasting your turbo streams as you want them in Rails 7" loading="lazy" width="2000" height="610" srcset="https://blog.kiprosh.com/content/images/size/w600/2022/12/Untitled-2022-12-11-2045.png 600w, https://blog.kiprosh.com/content/images/size/w1000/2022/12/Untitled-2022-12-11-2045.png 1000w, https://blog.kiprosh.com/content/images/size/w1600/2022/12/Untitled-2022-12-11-2045.png 1600w, https://blog.kiprosh.com/content/images/2022/12/Untitled-2022-12-11-2045.png 2373w" sizes="(min-width: 720px) 720px"><figcaption>Image depicting change in notification icon&#xA0;</figcaption></figure><p>How would you make sure that if an event occurs, the notification icon updates for everyone? You can create a turbo stream that replaces the icon and send that turbo stream to everyone. </p><p><strong>First</strong>, everyone has to listen to a <code>WebSocket</code> channel for incoming turbo streams. This is done using </p><!--kg-card-begin: markdown--><pre><code class="language-erb">&lt;%= turbo_stream_from(:notifications) %&gt;
</code></pre>
<!--kg-card-end: markdown--><p>where <code>:notifications</code> is just the name of the channel. <br></p><p><strong>Second</strong>, you broadcast the update using the special broadcasting methods provided by the <code><a href="https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb">Turbo::Broadcastable</a></code> concern.</p><!--kg-card-begin: markdown--><pre><code class="language-rb">broadcast_replace_to(:notifications, target: &quot;icon&quot;, html: &quot;&lt;span class=&apos;icon&apos;&gt;notification_new&lt;/span&gt;&quot;)
</code></pre>
<!--kg-card-end: markdown--><p>This method does two things</p><ol><li>Generates the turbo stream.</li><li>Broadcasts the turbo stream to all subscribers of the <code>notifications</code> channel.</li></ol><p>The broadcasted turbo stream looks something like this:</p><!--kg-card-begin: markdown--><pre><code class="language-erb">&lt;turbo-stream action=&quot;replace&quot; target=&quot;icon&quot;&gt;
    &lt;span class=&apos;icon&apos;&gt;notification_new&lt;/span&gt;
&lt;/turbo-stream&gt;
</code></pre>
<!--kg-card-end: markdown--><p>You can call this function in the controller or in the model after a callback like:</p><!--kg-card-begin: markdown--><pre><code class="language-rb"># app/models/Post.rb

class Post &lt; ApplicationRecord
  ...
  after_create_commit -&gt; { 
    broadcast_replace_to(
      :notifications,
      target: &quot;icon&quot;,
      html: &quot;&lt;span class=&apos;icon&apos;&gt;notification_new&lt;/span&gt;&quot;
      )
  }
  ...
end
</code></pre>
<!--kg-card-end: markdown--><p>Once a post is created, the icon for all the users will be replaced with a new one indicating a new notification. </p><h2 id="the-ways-of-broadcasting">The ways of Broadcasting</h2><p>So far, you have seen one type of broadcasting - <code>replace</code>. There are broadcasting functions for every type of turbo stream actions - &#xA0;<code>broadcast_update_to</code>, <code>broadcast_append_to</code> ... the entire list is <a href="https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb">here</a>. Let&apos;s see some of the common hurdles that you might face while using them.</p><h3 id="multiple-turbo-stream-actions-at-once">Multiple Turbo stream actions at once?</h3><p>If you want to broadcast multiple turbo stream actions at once, you can put all of your actions in one <code>*.turbo_stream.erb</code> file and broadcast the file!</p><!--kg-card-begin: markdown--><pre><code class="language-erb"># app/views/notifications/_new.turbo_stream.erb

&lt;%= turbo_stream.replace &quot;icon&quot; do %&gt;
   &lt;span class=&apos;icon&apos;&gt;notification_new&lt;/span&gt;
&lt;% end %&gt;
&lt;%= turbo_stream.append &quot;icon&quot; do %&gt;
   &lt;span class=&quot;count&quot;&gt;&lt;%= count %&gt;&lt;/span&gt;
&lt;% end %&gt;
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-rb"># app/models/Post.rb

class Post &lt; ApplicationRecord
  ...
  after_create_commit -&gt; { 
    broadcast_render_to(
      :notifications,
      partial: &quot;notifications/new&quot;
    )
  }
  ...
end
</code></pre>
<!--kg-card-end: markdown--><h3 id="selective-broadcasting">Selective broadcasting?</h3><p>If you want to multicast a turbo stream instead of broadcasting it, just make sure it reaches the right receivers. You can distinguish between your receivers by giving them their own channels. </p><p>For an example, different users are in different groups, and you want only the people inside the group to get the updates, how do you distinguish them from all the other users in the other groups?</p><!--kg-card-begin: markdown--><pre><code class="language-erb">&lt;%= turbo_stream_from(@group) %&gt;
</code></pre>
<!--kg-card-end: markdown--><p>This will create a unique channel for each group, and when you broadcast your updates,</p><!--kg-card-begin: markdown--><pre><code class="language-rb"># app/models/Post.rb

class Post &lt; ApplicationRecord
  belongs_to :group
  ...
  after_create_commit -&gt; { 
    broadcast_render_to(
      group,
      partial: &quot;notifications/new&quot;
    )
  ...
end
</code></pre>
<!--kg-card-end: markdown--><p>It will be broadcasted to only the required groups/channels.</p><h3 id="testing-your-broadcasts">Testing your broadcasts</h3><p>Of course, you would want to make sure your broadcasts are going through the correct channels but how do you test that? With <code>ActionCable</code> <a href="https://apidock.com/rails/v6.0.0/ActionCable/TestHelper/assert_broadcast_on">test helpers</a>.</p><!--kg-card-begin: markdown--><pre><code class="language-rb">test &quot;notifications are sent to same group&quot; do
    stream = @group
    target = &quot;icon&quot;

    assert_broadcast_on stream, turbo_stream_action_tag(&quot;replace&quot;, target: target, template: %(&lt;span class=&apos;icon&apos;&gt;notification_new&lt;/span&gt;)) do
      Post.create(title: &apos;A new post&apos;)
    end
end
</code></pre>
<!--kg-card-end: markdown--><h3 id="high-response-time">High response time!</h3><p>If the broadcasting of your turbo stream is an expensive operation, just create a <code>Turbo::Streams::BroadcastJob</code> to broadcast later in a background job. This is done using <code>broadcast_*_later_to</code> methods.</p><!--kg-card-begin: markdown--><pre><code class="language-rb"># app/models/Post.rb

class Post &lt; ApplicationRecord
  belongs_to :group
  ...
  after_create_commit -&gt; { 
    broadcast_render_later_to(
      group,
      partial: &quot;notifications/new&quot;
    )
  ...
end
</code></pre>
<!--kg-card-end: markdown--><p>You can try these examples in the demonstration app <a href="https://github.com/imanpalsingh/broadcasting_sample">here</a>. That&apos;s all about broadcasting for now. Until next time.</p>]]></content:encoded></item><item><title><![CDATA[Rails 7.1 supports password challenge via has_secure_password]]></title><description><![CDATA[From Rails 7.1 has_secure_password now includes a password_challenge accessor to validate it against the currently persisted password.]]></description><link>https://blog.kiprosh.com/has_secure_password-supports-password-challenge/</link><guid isPermaLink="false">6378dfe63cff3c20e60974a7</guid><category><![CDATA[rails_7]]></category><category><![CDATA[rails 7.1]]></category><category><![CDATA[password challenge]]></category><category><![CDATA[has_secure_password]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Ruby on Rails]]></category><dc:creator><![CDATA[Vishal Jain]]></dc:creator><pubDate>Mon, 19 Dec 2022 07:31:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHBhc3N3b3JkfGVufDB8fHx8MTY3MDE2MTgyNw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1633265486064-086b219458ec?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fHBhc3N3b3JkfGVufDB8fHx8MTY3MDE2MTgyNw&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Rails 7.1 supports password challenge via has_secure_password"><p>Rails provides the <em><em><code><a href="https://apidock.com/rails/v4.0.2/ActiveModel/SecurePassword/ClassMethods/has_secure_password">has_secure_password</a></code></em></em> method, which makes it gloriously easy to implement authentication in our application. <br>But we often need an extra layer of verification before allowing users to update certain fields. For e.g. Users must provide their &#x201C;old&#x201D; password when updating their email/password fields.</p><p></p><figure class="kg-card kg-image-card"><img src="https://blog.kiprosh.com/content/images/2022/12/image.png" class="kg-image" alt="Rails 7.1 supports password challenge via has_secure_password" loading="lazy" width="315" height="369"></figure><h3 id="before-rails-71">Before Rails 7.1</h3><p>To implement this, we must manually add and validate the <code>current_password</code> accessor:</p><pre><code class="language-ruby"># app/models/user.rb

class User &lt; ActiveRecord::Base
  has_secure_password

  attr_accessor :current_password
end</code></pre><pre><code class="language-ruby"># app/controllers/passwords_controller.rb

class PasswordsController &lt; ApplicationController
  def update
    password_challenge = password_params.delete(:current_password)
    @password_challenge_succeeded = current_user.authenticate(password_challenge)

    if @password_challenge_succeeded &amp;&amp; current_user.update(password_params)
      # ...
    end
  end

  private

  def password_params
    params.require(:user).permit(
      :password, 
      :password_confirmation, 
      :current_password
    )
  end
end</code></pre><pre><code class="language-erb"># app/views/users/password_edit.html.erb

&lt;%= form_for current_user do |f| %&gt;
  &lt;div class=&apos;mb-4&apos;&gt;
    &lt;%= f.label :current_password, &apos;Current Password&apos; %&gt;
    &lt;%= f.password_field :current_password %&gt;
    &lt;!-- Render error if @password_challenge_succeeded is false --&gt;
    &lt;% unless @password_challenge_succeeded %&gt;
      &lt;p class=&apos;error&apos;&gt;Current password is invalid.&lt;/p&gt;
    &lt;% end %&gt;
  &lt;/div&gt;

  &lt;div class=&apos;mb-4&apos;&gt;
    &lt;%= f.label :password, &apos;New Password&apos; %&gt;
    &lt;%= f.password_field :password %&gt;
  &lt;/div&gt;

  &lt;div class=&apos;mb-6&apos;&gt;
    &lt;%= f.label :password_confirmation %&gt;
    &lt;%= f.password_field :password_confirmation %&gt;
  &lt;/div&gt;

  &lt;div&gt;
    &lt;%= f.submit &apos;Update&apos; %&gt;
  &lt;/div&gt;
&lt;% end %&gt;</code></pre><h3 id="rails-71-onwards">Rails 7.1 onwards</h3><p><code>has_secure_password</code> now includes a <code>password_challenge</code> accessor and its corresponding validation. So we no longer need our own accessor and can use <code>password_challenge</code> to verify the existing password.<br>When <code>password_challenge</code> is set, it will validate against the currently persisted password digest (i.e. <code>password_digest_was</code>).</p><pre><code class="language-ruby"># app/models/user.rb

class User &lt; ActiveRecord::Base
  has_secure_password
end</code></pre><pre><code class="language-ruby"># app/controllers/passwords_controller.rb

class PasswordsController &lt; ApplicationController
  def update
    if current_user.update(password_params)
      # ...
    end
  end

  private

  def password_params
    # Note: password_challenge will validate only if it is set to a value other than nil.
    params.require(:user).permit(
      :password, 
      :password_confirmation, 
      :password_challenge
    ).with_defaults(password_challenge: &apos;&apos;)
  end
end</code></pre><p>And in the views we can replace <code>current_password</code> field with <code>password_challenge</code>.</p><pre><code class="language-erb"># app/views/users/password_edit.html.erb

&lt;%= form_for current_user do |f| %&gt;
  &lt;div class=&apos;mb-4&apos;&gt;
    &lt;%= f.label :password_challenge, &apos;Current Password&apos; %&gt;
    &lt;%= f.password_field :password_challenge %&gt;
    &lt;% if f.object.errors[:password_challenge].present? %&gt;
      &lt;p class=&apos;error&apos;&gt; &lt;%= f.object.errors[:password_challenge].first %&gt; &lt;/p&gt;
    &lt;% end %&gt;
  &lt;/div&gt;
    .
    .
    .
&lt;% end %&gt;</code></pre><p>This allows <code>password_challenge</code> to be implemented with the same ease as the <code>password_confirmation</code>. If the <code>password_challenge</code> fails, a validation error will be added just like any other attribute.</p><figure class="kg-card kg-image-card"><img src="https://blog.kiprosh.com/content/images/2022/12/image-2.png" class="kg-image" alt="Rails 7.1 supports password challenge via has_secure_password" loading="lazy" width="325" height="388"></figure><p>Check out this <a href="https://github.com/rails/rails/pull/43688">pull request</a> for more details.</p>]]></content:encoded></item><item><title><![CDATA[Rails 7.1 allows templates to set strict locals]]></title><description><![CDATA[Rails 7.1 adds the ability to define the number of locals a template can accept. To achieve this, add a locals magic comment inside the partial.]]></description><link>https://blog.kiprosh.com/allow-template-to-set-strict-locals/</link><guid isPermaLink="false">6370f4463cff3c20e6097266</guid><category><![CDATA[rails_7]]></category><category><![CDATA[rails 7.1]]></category><category><![CDATA[action-view]]></category><dc:creator><![CDATA[Manoj Saun]]></dc:creator><pubDate>Wed, 07 Dec 2022 07:25:11 GMT</pubDate><content:encoded><![CDATA[<p>As our view files get larger, we make use of partial templates to move some code to its own file. This also helps us to follow the single-responsibility principle. Partials also accept local variables, enhancing their flexibility and power.</p><h3 id="before-rails-71"><strong>Before Rails 7.1</strong></h3><p>Prior to Rails 7.1, we could not restrict which local variables we can pass to the partials.</p><h3 id="rails-71-onwards"><strong>Rails 7.1 onwards</strong></h3><p>Rails 7.1 <a href="https://github.com/rails/rails/pull/45602">adds the ability</a> to define the local variables a template can accept. This helps in precompiling templates during application startup rather than at runtime. To achieve this, we have to add a locals magic comment inside the partial.</p><pre><code class="language-ruby"># app/views/dashboard/_contact_us.html.erb

&lt;%# locals: (address:, mobile:) -%&gt;

&lt;%= address %&gt;
&lt;%= mobile %&gt;</code></pre><p>With the help of the magic comment, we have defined only two locals <code>address</code> &amp; <code>mobile</code>. This can be passed to the partial as follows:</p><pre><code class="language-ruby"># app/views/homepage/index.html.erb

&lt;%= render partial: &apos;contact_us&apos;, locals: { address: &apos;Mumbai&apos;, mobile: &apos;987654321&apos; } %&gt;</code></pre><p>Now, if we try to pass an additional local variable, let&apos;s say <code>company</code></p><pre><code class="language-ruby"># app/views/homepage/index.html.erb

&lt;%= render partial: &apos;contact_us&apos;, locals: { address: &apos;Mumbai&apos;, mobile: &apos;987654321&apos;, company: &apos;Test Company&apos; } %&gt;</code></pre><p>The above will throw the following error in the server log</p><pre><code class="language-shell">ActionView::Template::Error (unknown local: :company):
  
app/views/homepage/index.html.erb:2</code></pre><p>Also, if we define a local variable but do not pass it while rendering the partial, for instance, let&apos;s fail to pass the defined local variable <code>mobile</code> on rendering the partial</p><pre><code class="language-ruby"># app/views/homepage/index.html.erb

&lt;%= render partial: &apos;contact_us&apos;, locals: { address: &apos;Mumbai&apos; } %&gt;</code></pre><p>The server log will warn us with the following error</p><pre><code class="language-shell">ActionView::Template::Error (missing local: :mobile):

app/views/homepage/index.html.erb:2</code></pre><h4 id="passing-default-values"><strong>Passing default values</strong></h4><p>We can also pass a default value to the locals as follows:</p><pre><code class="language-ruby"># app/views/dashboard/_contact_us.html.erb

&lt;%# locals: (address: &apos;Delhi&apos;, mobile: 1234567890) -%&gt;

&lt;%= address %&gt;
&lt;%= mobile %&gt;</code></pre><p>Now, if we missed out to pass the locals while rendering the template/partial, it will use the default value assigned by the magic comment.</p><h4 id="disabling-locals"><strong>Disabling locals</strong></h4><p>We can disable passing any locals to the partial. Add the following magic comment to the partial.</p><pre><code class="language-ruby"># app/views/dashboard/_contact_us.html.erb

&lt;%# locals: () %&gt;

&lt;h1&gt;Contact Us&lt;/h1&gt;
&lt;p&gt;Mumbai&lt;/p&gt;
&lt;p&gt;1234567890&lt;/p&gt;</code></pre><p>Now, if we try to pass locals while rendering the above partial, the server log will throw the below error.</p><pre><code class="language-shell">ActionView::Template::Error (no locals accepted):
  
app/views/homepage/index.html.erb:2</code></pre><h2 id="references"><strong>References</strong></h2><ul><li><a href="https://github.com/rails/rails/pull/45602">GitHub PR: Allow templates to define which locals they accept</a></li><li><a href="https://github.com/rails/rails/blob/main/actionview/CHANGELOG.md\">Rails 7.1 ActionView Changelog</a></li><li><a href="https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials">RubyOnRails: Layouts and Rendering</a></li></ul>]]></content:encoded></item></channel></rss>