<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucas M.</title>
    <description>The latest articles on DEV Community by Lucas M. (@lcsm0n).</description>
    <link>https://dev.to/lcsm0n</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F928798%2Fcbba1850-b59c-49b2-bc74-fde91a96e2cc.png</url>
      <title>DEV Community: Lucas M.</title>
      <link>https://dev.to/lcsm0n</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lcsm0n"/>
    <language>en</language>
    <item>
      <title>How to set a timeout to RSpec test executions</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Thu, 24 Jul 2025 16:30:42 +0000</pubDate>
      <link>https://dev.to/lcsm0n/how-to-set-a-timeout-to-rspec-test-executions-2gc9</link>
      <guid>https://dev.to/lcsm0n/how-to-set-a-timeout-to-rspec-test-executions-2gc9</guid>
      <description>&lt;blockquote&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Tired of tests hanging indefinitely in your CI pipeline, wasting resources and delaying feedback? I built &lt;strong&gt;RspecTimeGuard&lt;/strong&gt;, a new gem that adds timeout protection to your RSpec test suite. Set time limits on individual tests or globally, and get clear error messages when tests exceed their limits instead of waiting for your CI to time out.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Check it out&lt;/strong&gt;: &lt;a href="https://github.com/LucasMontorio/rspec-time-guard" rel="noopener noreferrer"&gt;https://github.com/LucasMontorio/rspec-time-guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is my first gem, so any stars are more than welcome! ⭐&lt;/p&gt;

&lt;p&gt;The article below walks through the technical journey of building this solution, from initial thread-joining approaches to the final single-threaded monitoring implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction / Motivation
&lt;/h2&gt;

&lt;p&gt;On one of the Rails application on which I am working, a Rails monolith, I recently stumbled upon a quite disturbing CI issue: in some of our RSpec runs, it happens that specs &lt;strong&gt;timeout in an unpredictable way after having reached the CI environment’s (in our case, CircleCI) built-in “no-output timeout”&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I won’t go in depth into the nature of these tests, but let’s say they are simply browser-centric Capybara tests that sometimes get stuck waiting for a condition that can never be satisfied.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reasons that could lead to such hangs are multiple: deadlocks, connection pools exhausted, infinite loops…&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;When these cases happen, the test that hangs terminates with a quite useless output  and causes your CI run to wait for up to 10 minutes just to tell you that your test has failed. This can cause important delays and generate extra costs (CI runs being usually billed by the minute).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt; &lt;span class="nx"&gt;timed&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made me realise that, some times, it could be good to be able to make a test fail after a certain amount of time, be it to prevent the CI env. from spending too much time (and money) on it, or simply to monitor the efficiency of a test suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To my surprise, I didn’t find any simple, RSpec built-in way to do this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hence, I started developing my own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article will go into the details of the strategy adopted for this solution, and the different phases through which I went before reaching a satisfactory implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  First iteration / Thread-joining
&lt;/h2&gt;

&lt;p&gt;In order to achieve this, my first intention was to create a mechanism that could detect that a test has been running for too long, then terminate it with a helpful error message that would appear in the CI’s output.&lt;/p&gt;

&lt;p&gt;One way of doing this would be to instantiate a thread responsible of running the test, and monitor its execution from the main thread. When reaching a timeout, we could then kill the running thread and proceed with the rest of the suite.&lt;/p&gt;

&lt;p&gt;Ruby’s &lt;code&gt;Thread#join&lt;/code&gt; method allows us to make the main thread wait until the thread referenced by the method completes its execution. It also accepts a timeout parameter, which is particularly relevant to our case. When a timeout is provided, the method will wait only up to that many seconds for the thread to complete:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a new thread that takes 5 seconds&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Thread completed"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Wait for at most 2 seconds for the thread to complete&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Thread completed within timeout"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Thread did not complete within timeout"&lt;/span&gt;
  &lt;span class="c1"&gt;# The thread is still running in the background&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Main thread continues regardless"&lt;/span&gt;

&lt;span class="c1"&gt;# OUTPUT:&lt;/span&gt;
&lt;span class="no"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;did&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;
&lt;span class="no"&gt;Main&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="n"&gt;continues&lt;/span&gt; &lt;span class="n"&gt;regardless&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In this example, &lt;code&gt;t.join(2)&lt;/code&gt; will return &lt;code&gt;nil&lt;/code&gt; because the thread doesn't complete within 2 seconds. If the thread had completed before the timeout, it would have returned the thread object itself, which evaluates to &lt;code&gt;true&lt;/code&gt; in a boolean context.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Now that I could identify a thread running for too long, I simply had to add a wrapper (hook) around RSpec examples to plug this behaviour on every running spec, and cleanly terminate the timed-out ones.&lt;/p&gt;

&lt;p&gt;This lead to the &lt;strong&gt;first, basic implementation of the gem&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;time_limit_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TimeLimitExceededError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s2"&gt;"[RspecTimeGuard] Example exceeded timeout of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;At this stage, a timed-out test generates the following output:&lt;/em&gt;&lt;br&gt;

  Show error output
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Randomized with seed 29957
&lt;span class="c"&gt;#&amp;lt;Thread:0x000000015bf7de68 ... exception (report_on_exception is true):&lt;/span&gt;
...gems/rspec-core-3.13.3/lib/rspec/core/example.rb:521:in &lt;span class="sb"&gt;`&lt;/span&gt;ensure &lt;span class="k"&gt;in &lt;/span&gt;run_after_example&lt;span class="s1"&gt;': undefined method `teardown_mocks_for_rspec'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;nil &lt;span class="o"&gt;(&lt;/span&gt;NoMethodError&lt;span class="o"&gt;)&lt;/span&gt;

        @example_group_instance.teardown_mocks_for_rspec
                               ^^^^^^^^^^^^^^^^^^^^^^^^^

RspecTimeGuard::TimeLimitExceededError: &lt;span class="o"&gt;[&lt;/span&gt;RspecTimeGuard] Example exceeded &lt;span class="nb"&gt;timeout &lt;/span&gt;of 1 seconds

0&lt;span class="o"&gt;)&lt;/span&gt; Processing::Base#call raises a &lt;span class="nb"&gt;timeout &lt;/span&gt;error
   Failure/Error: raise RspecTimeGuard::TimeLimitExceededError, message

   RspecTimeGuard::TimeLimitExceededError:
   &lt;span class="o"&gt;[&lt;/span&gt;RspecTimeGuard] Example exceeded &lt;span class="nb"&gt;timeout &lt;/span&gt;of 1 seconds
   &lt;span class="c"&gt;# ... (2 levels) in setup'&lt;/span&gt;
1 example, 1 failure, 0 passed
Finished &lt;span class="k"&gt;in &lt;/span&gt;1.033112 seconds

Randomized with seed 29957

Process finished with &lt;span class="nb"&gt;exit &lt;/span&gt;code 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;br&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  Cleaning the error trace / better error-handling
&lt;/h3&gt;

&lt;p&gt;Despite being able to make a test fail based on its execution time, this would still generate quite a lot of noise, due to our overwriting some of RSpec’s internal logic.&lt;/p&gt;

&lt;p&gt;In order to avoid this, there exists an option that prevents the noisy error output by preventing Ruby from automatically reporting exceptions that occur within the thread.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When using &lt;code&gt;thread.kill&lt;/code&gt; to terminate the test thread, Ruby was automatically printing all the internal exceptions that happened during the termination process. This created the extremely verbose and confusing stack trace seen in the example, showing internal RSpec errors like "undefined method teardown_mocks_for_rspec' for nil".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;By setting &lt;code&gt;Thread.current.report_on_exception = false&lt;/code&gt; at the beginning of our thread creation, I could tell Ruby not to automatically print these internal exceptions to stderr.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This setting doesn't suppress the exceptions themselves - they still occur and can be caught and handled - it just prevents their automatically being printed to the console, which was cluttering our output with information that wasn't helpful to users of the gem.*&lt;br&gt;
Beyond avoiding noisy process terminations, I also needed to properly handle exceptions and allow them to re-raise in the main thread to allow RSpec to fail for reasons unrelated to timeouts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;If a test fails for reasons unrelated to timeouts (like a failing assertion), we want that original error to be reported clearly, not obscured by our timeout machinery.&lt;br&gt;
To improve thread termination, we explored using &lt;code&gt;thread.exit&lt;/code&gt; as a gentler alternative to &lt;code&gt;thread.kill&lt;/code&gt;. While &lt;code&gt;thread.kill&lt;/code&gt; forces immediate termination and can leave resources in an inconsistent state, &lt;code&gt;thread.exit&lt;/code&gt; requests the thread to terminate more cooperatively.&lt;/p&gt;

&lt;p&gt;This lead roughly to the following (simplified) implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report_on_exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:exception&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Later in the code:&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:exception&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:exception&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TimeLimitExceededError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"[RspecTimeGuard] Example exceeded timeout of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;terminate_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;

  &lt;span class="c1"&gt;# Attempt to terminate the thread gracefully&lt;/span&gt;
  &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;

  &lt;span class="c1"&gt;# Give the thread a moment to exit gracefully and perform cleanup&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
  &lt;span class="c1"&gt;# If it's still alive, kill it&lt;/span&gt;
  &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;For the same timed-out spec as mentioned above, we now get the following output:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;
  Show improved error output
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RspecTimeGuard::TimeLimitExceededError: &lt;span class="o"&gt;[&lt;/span&gt;RspecTimeGuard] Example exceeded &lt;span class="nb"&gt;timeout &lt;/span&gt;of 1 seconds

0&lt;span class="o"&gt;)&lt;/span&gt; Processing::Base#call raises a &lt;span class="nb"&gt;timeout &lt;/span&gt;error
   Failure/Error: raise RspecTimeGuard::TimeLimitExceededError, message

   RspecTimeGuard::TimeLimitExceededError:
   &lt;span class="o"&gt;[&lt;/span&gt;RspecTimeGuard] Example exceeded &lt;span class="nb"&gt;timeout &lt;/span&gt;of 1 seconds
   &lt;span class="c"&gt;# .../lib/rspec_time_guard.rb:53:in `block (2 levels) in setup'&lt;/span&gt;
1 example, 1 failure, 0 passed
Finished &lt;span class="k"&gt;in &lt;/span&gt;1.127036 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;RSpec.current_example&lt;/code&gt; issue / better thread handling
&lt;/h3&gt;

&lt;p&gt;Now that I was able to set an example-specific timeout threshold and get a clean error output in case of timeout, the next step was to allow the gem to &lt;strong&gt;set a global value for this threshold&lt;/strong&gt;, thus allowing us to run an entire test suite with the same timing expectations.&lt;/p&gt;

&lt;p&gt;In the traditional Ruby gem fashion, I implemented a configuration object responsible for storing the timeout threshold value globally, and ran the test suite locally on a production-ready application with &amp;gt;1000 tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;time_limit_seconds &lt;span class="o"&gt;=&lt;/span&gt; example.metadata[:time_limit_seconds] &lt;span class="o"&gt;||&lt;/span&gt; RspecTimeGuard.configuration.max_execution_time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;To my surprise, I ran into several issues that led to a significant proportion of my examples ending with the following error when setting the global &lt;code&gt;max_execution_time&lt;/code&gt; param: tests that had previously worked fine suddenly began failing with puzzling errors indicating that &lt;code&gt;RSpec.current_example&lt;/code&gt; was nil. This was particularly perplexing since the tests had worked correctly when using per-example time limits through metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;      NoMethodError:
        undefined method &lt;span class="sb"&gt;`&lt;/span&gt;metadata&lt;span class="s1"&gt;' for nil
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Further investigation revealed that some of our tests were trying to access &lt;code&gt;RSpec.current_example.metadata&lt;/code&gt; as part of their business, which caused the error. The intention behind these tests will not be described here, but let’s just say that they needed to access the current example’s metadata to conditionally load certain fixtures. It turned out this getter was actually broken because of my &lt;code&gt;RspecTimeGuard&lt;/code&gt; implementation. Here’s why:&lt;/p&gt;

&lt;p&gt;The root cause lay in how RSpec manages the current example context. &lt;strong&gt;RSpec uses thread-local storage&lt;/strong&gt; to track the currently executing example, making it available via &lt;code&gt;RSpec.current_example&lt;/code&gt;. This works well in standard RSpec usage where tests run in the main thread, but our implementation was breaking this fundamental assumption by &lt;strong&gt;running each test in a separate thread so that the main thread could monitor its execution time:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report_on_exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:exception&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# Handle timeout...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This design created a disconnection: when the test called &lt;code&gt;RSpec.current_example&lt;/code&gt;, it returned nil &lt;strong&gt;because the thread-local reference only existed in RSpec's original thread&lt;/strong&gt;, not in my custom thread where the test was actually running. I had to update the gem’s design and invert the approach.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Since I had to keep the test running in RSpec’s main thread where all the context was properly set up, I could now use a separate thread solely for timeout monitoring.&lt;br&gt;
This inverted design became the new implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;time_limit_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:time_limit_seconds&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global_time_limit_seconds&lt;/span&gt;

    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;

    &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

    &lt;span class="c1"&gt;# NOTE: We instantiate a monitoring thread, to allow the example to run in the main RSpec thread.&lt;/span&gt;
    &lt;span class="c1"&gt;# This is required to keep the RSpec context.&lt;/span&gt;
    &lt;span class="n"&gt;monitor_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report_on_exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="c1"&gt;# NOTE: The following logic:&lt;/span&gt;
      &lt;span class="c1"&gt;#  - Waits for the duration of the time limit&lt;/span&gt;
      &lt;span class="c1"&gt;#  - If the main thread is still running at that stage, raises a TimeLimitExceededError&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;completed&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"[RspecTimeGuard] Example exceeded timeout of &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;time_limit_seconds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;continue_on_timeout&lt;/span&gt;
          &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - Running the example anyway (:continue_on_timeout option set to TRUE)"&lt;/span&gt;
          &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;RspecTimeGuard&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TimeLimitExceededError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# NOTE: Main RSpec thread execution&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
      &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="c1"&gt;# NOTE: We explicitly clean up the monitoring thread in case the example completes before the time limit.&lt;/span&gt;
      &lt;span class="n"&gt;monitor_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;monitor_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The monitoring thread simply sleeps for the duration of the timeout&lt;/strong&gt; and then checks if the test is still running. If it is, it raises an error in the main thread to terminate the test.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A key component of this implementation is the &lt;code&gt;completed&lt;/code&gt; flag that provides communication between the main thread and the monitoring thread. When the test finishes successfully, the main thread sets this flag to true, signaling to the monitoring thread that it shouldn't take any action even if it wakes up after its sleep period.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This redesign solved the &lt;code&gt;RSpec.current_example&lt;/code&gt; issue!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Second iteration / single-thread monitoring implementation
&lt;/h3&gt;

&lt;p&gt;Now that the the RSpec example context issue was fixed, I was finally able to finalise a first (WIP) version of the gem: add specs, a CI suite, a proper README, … And finally test it on a production app’s CI run.&lt;/p&gt;

&lt;p&gt;Although this was a nice milestone, the latest implementation at this staged still was unsatisfying in a way: &lt;strong&gt;Each test was creating its own monitoring thread&lt;/strong&gt;, and in test suites with thousands of examples, this could potentially create thousands of threads over the course of execution. This proliferation of threads could impact system resources and overall performance.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;To address this issue, my idea was to reimagine the current implementation to use a a &lt;strong&gt;single, persistent monitoring thread&lt;/strong&gt; that would track all active tests. Instead of each test creating its own monitor, tests would register themselves with this central monitor when they started and unregister when they completed.&lt;/p&gt;

&lt;p&gt;I encapsulated this functionality in a &lt;code&gt;TimeoutMonitor&lt;/code&gt; class with a clean API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeoutMonitor&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Add test to the active test list with its metadata&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unregister_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Remove test from the active test list&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;This approach required careful attention to thread safety, as multiple tests could be registering and unregistering concurrently. I used a mutex to ensure that updates to the active tests list were atomic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@active_tests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;start_time: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;thread_id: &lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;warned: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;start_monitor_thread&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@monitor_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@monitor_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The backbone of the monitoring system was now a single thread that periodically checked the list of active tests to see if they have reached their respective time limits. This thread would be initiated in our RSpec &lt;code&gt;around&lt;/code&gt; block as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_monitor_thread&lt;/span&gt;
  &lt;span class="vi"&gt;@monitor_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rspec_time_guard_monitor'&lt;/span&gt;

    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;check_for_timeouts&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="c1"&gt;# Check every half second&lt;/span&gt;

      &lt;span class="c1"&gt;# Exit thread if no more tests to monitor&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@active_tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;For each active test, the monitor compared its elapsed running time against its specified timeout limit. If a test had exceeded its limit, the monitor would take appropriate action, either raising an error or outputting a warning depending on configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_for_timeouts&lt;/span&gt;
  &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
  &lt;span class="n"&gt;timed_out_examples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="vi"&gt;@mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="vi"&gt;@active_tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:start_time&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;timed_out_examples&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:timeout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Handle timeouts outside the mutex to avoid deadlocks&lt;/span&gt;
  &lt;span class="n"&gt;timed_out_examples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Create error message and handle timeout...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;One challenge I faced was how to &lt;strong&gt;track thread objects across different contexts&lt;/strong&gt;. Storing direct references to thread objects could lead to memory leaks, so I instead stored thread IDs and used &lt;code&gt;ObjectSpace._id2ref&lt;/code&gt; to retrieve the actual thread when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;ObjectSpace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_id2ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@active_tests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object_id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="ss"&gt;:thread_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To improve the user experience when using the &lt;code&gt;continue_on_timeout&lt;/code&gt; option, I added a &lt;code&gt;warned&lt;/code&gt; flag to track which tests had already received timeout warnings. This prevented the monitor from outputting repeated warnings for the same test.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Finally, a tiny bit of resource conservation logic. The monitor thread automatically terminates itself when there are no more active tests to monitor, and it only starts when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Exit thread if no more tests to monitor&lt;/span&gt;
&lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@active_tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single-threaded monitoring approach &lt;strong&gt;dramatically reduced the number of threads&lt;/strong&gt; created during test suite execution, from potentially thousands to just one.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Notes / final comments
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;RspecTimeGuard&lt;/code&gt; was developed and tested primarily with MRI/CRuby, but we gave careful consideration to compatibility with other Ruby interpreters. The gem's core functionality relies on standard Ruby libraries and APIs that are implemented across different Ruby interpreters, making it broadly compatible in most scenarios.&lt;/p&gt;

&lt;p&gt;However, there are some potential compatibility considerations worth noting. The gem's use of &lt;code&gt;Thread.raise&lt;/code&gt; behavior can vary between interpreters - while it works reliably in MRI/CRuby, JRuby and TruffleRuby may exhibit different timing characteristics and reliability patterns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Eventually, &lt;code&gt;RspecTimeGuard&lt;/code&gt; represents a new gem that provides a simple, focused solution to a quite common problem in Ruby test suites - the need to prevent tests from hanging indefinitely and wasting CI resources. Through its evolution from a basic thread-joining approach to a sophisticated single-threaded monitoring system, it taught me a lot of interesting things and made me want to share this project, which is the point of this whole article!&lt;/p&gt;

&lt;p&gt;The gem doesn't pretend to be perfect, and we actively welcome challenging comments, issues, and contributions from the community. Real-world usage will undoubtedly reveal edge cases and improvement opportunities that we haven't yet encountered.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In the coming months, &lt;code&gt;RspecTimeGuard&lt;/code&gt; will undergo more intensive testing across different Ruby applications, environments, and use cases. I plan to write more on the outcome of such usage. In the meantime, I hope you enjoyed reading this as much as I enjoyed writing it!&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Cheers,&lt;/p&gt;

&lt;p&gt;Lucas&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Rails transactional callbacks beyond models</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Tue, 10 Dec 2024 17:35:23 +0000</pubDate>
      <link>https://dev.to/lcsm0n/rails-transactional-callbacks-d0d</link>
      <guid>https://dev.to/lcsm0n/rails-transactional-callbacks-d0d</guid>
      <description>&lt;p&gt;In this previous &lt;a href="https://dev.to/lcsm0n/transaction-safety-in-rails-identifying-and-addressing-non-atomic-interactions-4kf8"&gt;post&lt;/a&gt;, we discussed non-atomic interactions in transactions and their potential impact on data integrity.&lt;br&gt;
One of the workarounds proposed for the issue described there was to use a &lt;strong&gt;transactional callback.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Let’s deep-dive into this feature, see how to make maximum profit of it, and how to extend its principle outside of the ActiveRecord with the help of a gem.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;
  
  
  Legacy / Model Callbacks
&lt;/h2&gt;

&lt;p&gt;Rails callbacks (or, more precisely, &lt;code&gt;ActiveRecord&lt;/code&gt; callbacks) have been a common and handy practice for triggering logic on a range of events that can occur on a given model.&lt;br&gt;
However, it's important to note that some of them rely on potentially risky patterns that are sometimes overlooked, despite posing risks to data integrity.&lt;/p&gt;

&lt;p&gt;According to the official definition,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic before or after a change in the object state”.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the list of “classic” supported callbacks available on ActiveRecord:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;before_validation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;after_validation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;before_save&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;before_create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;after_create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;after_save&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle here is simple: when firing a &lt;code&gt;create&lt;/code&gt; , &lt;code&gt;save&lt;/code&gt; or a validation event on the &lt;code&gt;ActiveRecord&lt;/code&gt; model at stake, the event will be preceded or followed by the execution of the block passed to the callback definition.&lt;/p&gt;

&lt;p&gt;Hence, their use-cases can be multiple: sending an email after an object is created, formatting a user’s phone number before persisting it, updating relations subsequently to a successful action…&lt;/p&gt;

&lt;p&gt;Here is the example we saw in the previous article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:do_something_asynchronous&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something_asynchronous&lt;/span&gt;
    &lt;span class="no"&gt;SyncUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Some business logic there...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We observed that doing this represents a non-atomic interaction that, when run inside a transaction (which can implicitly happen as soon as a transaction is opened that includes manipulations on the &lt;code&gt;user&lt;/code&gt; here), can lead to &lt;strong&gt;race conditions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Transactional Callbacks
&lt;/h2&gt;

&lt;p&gt;To avoid this type of race condition, we can replace this &lt;code&gt;after_save&lt;/code&gt; callback with an &lt;code&gt;after_commit&lt;/code&gt; callback, which will only fire after the transaction is completed.&lt;/p&gt;

&lt;p&gt;This is called a &lt;strong&gt;transactional callback&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The available transactional callbacks to date are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;after_commit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;after_rollback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Transactional callbacks are native to ActiveRecord models and follow a simple principle: instead of relying on a model-related event, they rely on the state of an ActiveRecord transaction, meaning that any block of logic passed to it will be registered and called only when the transaction is closed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;It is important to note that an &lt;code&gt;after_commit&lt;/code&gt; statement is not specific to transactions on the current model, but will wait for every level of nested transactions around it to be closed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s see an example, involving two models and an asynchronous job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:do_something_asynchronous&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something_asynchronous&lt;/span&gt;
    &lt;span class="no"&gt;SyncUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:log_something&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_something&lt;/span&gt;
    &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Post with ID &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was committed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SyncUser&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="s1"&gt;'NEW CONTENT'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Some business logic on the user and post...&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"ASYNC action on user with ID: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;When executing the following code in a console...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rails console&lt;/span&gt;
&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'John DOE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

  &lt;span class="c1"&gt;# We wait for 5 sec to simulate a (very) long transaction&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#  TRANSACTION (1.0ms)  BEGIN&lt;/span&gt;
&lt;span class="c1"&gt;#  User Create (1.1ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "John DOE"], ["created_at", "2024-12-05 18:10:51.580199"], ["updated_at", "2024-12-05 18:21:24.721670"]]&lt;/span&gt;
&lt;span class="c1"&gt;#  TRANSACTION (0.9ms)  COMMIT&lt;/span&gt;

&lt;span class="c1"&gt;# Sidekiq server&lt;/span&gt;
&lt;span class="c1"&gt;# Performing SyncUserJob [...] from Sidekiq [...] enqueued at 2024-12-05T18:21:29Z with arguments: {:user_id=&amp;gt;1}&lt;/span&gt;
&lt;span class="c1"&gt;# Post with ID 1 was committed at: 2024-12-05T19:21:30+01:00&lt;/span&gt;
&lt;span class="c1"&gt;# Performed SyncUserJob [...] in 5071.17ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the timestamps of user update, job enqueuing, &lt;code&gt;Post&lt;/code&gt;'s callback logging, and job ending, we can deduce that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;user&lt;/code&gt; was persisted while the transaction on &lt;code&gt;Post&lt;/code&gt; was still open (you don't have the timestamp of my tapping ENTER in my console, but believe me, this DB operation was instantly committed)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;after_commit&lt;/code&gt; callback on &lt;code&gt;User&lt;/code&gt; waited 5 seconds before being triggered, resulting in the Sidekiq job being enqueued 5 seconds later than the &lt;code&gt;user&lt;/code&gt;'s creation date. We can deduce from this part that the transactional callback is not restricted to a DB transaction on the table linked to the model bearing the callback, but waits for any open transaction to be closed.&lt;/li&gt;
&lt;li&gt;The job was quickly performed (~1 second after being enqueued), and the callback on &lt;code&gt;Post&lt;/code&gt; was triggered right away, as a result of the &lt;code&gt;Post.first.update(...)&lt;/code&gt; operation it contains&lt;/li&gt;
&lt;li&gt;The job then slept for 5 seconds before ending. At this stage, no ongoing transaction remained open&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Breaking down this example on a classic ActiveRecord model highlights the benefits of transactional callbacks to ensure data integrity throughout your model’s lifecycle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would add that relying on callbacks remains a tricky practice, and shouldn’t be generalised. In many cases, using an interaction pattern or a simple service object to encapsulate DB manipulations and their side-effects might be the most efficient and straightforward move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now, what if we wanted to run a callback on the event of the successful execution of some service object, outside of a specific model?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  The after_commit_everywhere gem
&lt;/h2&gt;

&lt;p&gt;Let's illustrate this case with the following example of a service that manipulates &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;users&lt;/code&gt; simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BusinessLogicService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'transaction ongoing'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What if I want to send an email (or perform any other action) as soon as this transaction is committed?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since we are not in an ActiveRecord model, we can't use the &lt;code&gt;after_commit&lt;/code&gt; callback mentioned above.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fortunately, a great gem called &lt;code&gt;after_commit_everywhere&lt;/code&gt; (GitHub project &lt;a href="https://github.com/Envek/after_commit_everywhere" rel="noopener noreferrer"&gt;here&lt;/a&gt;) offers a plug-and-play solution to tackle this case.&lt;/p&gt;

&lt;p&gt;The principle is simple: include the &lt;code&gt;AfterCommitEverywhere&lt;/code&gt; module, and you'll have access to &lt;code&gt;after_commit&lt;/code&gt;, &lt;code&gt;after_rollback&lt;/code&gt;, and &lt;code&gt;before_commit&lt;/code&gt; "callbacks" wherever you need them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;AfterCommitEverywhere&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BusinessLogicService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'transaction over!'&lt;/span&gt;
        &lt;span class="c1"&gt;# do something&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'transaction ongoing'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Okay, but how does this work under the hood?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's see what a call to &lt;code&gt;after_commit&lt;/code&gt; does, in the &lt;a href="https://github.com/Envek/after_commit_everywhere/blob/master/lib/after_commit_everywhere.rb#L41" rel="noopener noreferrer"&gt;gem's source code&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It "registers a callback" in the form of a block or proc

&lt;ul&gt;
&lt;li&gt;Checks if a transaction is active

&lt;ul&gt;
&lt;li&gt;Gets the active &lt;code&gt;ActiveRecord::Base.connection&lt;/code&gt; if any, defines one otherwise&lt;/li&gt;
&lt;li&gt;Calls &lt;a href="https://apidock.com/rails/v4.2.7/ActiveRecord/ConnectionAdapters/DatabaseStatements/transaction_open%3F" rel="noopener noreferrer"&gt;&lt;code&gt;ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction_open?&lt;/code&gt;&lt;/a&gt; on the connection&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If a transaction is active

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;prepend&lt;/code&gt; option is passed

&lt;ul&gt;
&lt;li&gt;Fetches the array of &lt;code&gt;records&lt;/code&gt; (models with callbacks defined on them) on the connection's &lt;code&gt;current_transaction&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;else (general case)

&lt;ul&gt;
&lt;li&gt;Wraps the callback in an ActiveRecord model-like class called &lt;code&gt;Wrap&lt;/code&gt;, to be able to register transactional callbacks on it. This is the trick that allows usage of ActiveRecord's native callbacks.&lt;/li&gt;
&lt;li&gt;Calls &lt;a href="https://apidock.com/rails/v4.0.2/ActiveRecord/ConnectionAdapters/DatabaseStatements/add_transaction_record" rel="noopener noreferrer"&gt;&lt;code&gt;ActiveRecord::ConnectionAdapters::DatabaseStatements#add_transaction_record&lt;/code&gt;&lt;/a&gt; on the connection with the wrapped callback&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;If no transaction is active

&lt;ul&gt;
&lt;li&gt;Yields the callback or raises, depending on config&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As you can see, what I like about this gem is that it relies only on ActiveRecord's internals and simply exposes them in a simple, plug-and-play module.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;As a conclusion, transactional callbacks offer a powerful tool for maintaining data integrity and ensuring atomicity in complex DB operations.&lt;/p&gt;

&lt;p&gt;With the help of &lt;code&gt;after_commit_everywhere&lt;/code&gt;, we can take this concept further and use them outside of &lt;code&gt;ActiveRecord&lt;/code&gt; models, &lt;strong&gt;but&lt;/strong&gt;, it's important to remember that over-reliance on callbacks can lead to tightly coupled code and potential performance issues.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>cleancode</category>
      <category>learning</category>
    </item>
    <item>
      <title>Transaction Safety in Rails: Identifying and Addressing Non-Atomic Interactions</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Thu, 21 Nov 2024 17:23:37 +0000</pubDate>
      <link>https://dev.to/lcsm0n/transaction-safety-in-rails-identifying-and-addressing-non-atomic-interactions-4kf8</link>
      <guid>https://dev.to/lcsm0n/transaction-safety-in-rails-identifying-and-addressing-non-atomic-interactions-4kf8</guid>
      <description>&lt;p&gt;Database transactions are a crucial mechanism for maintaining data integrity in the face of unexpected errors or failures. They ensure that multiple related operations are treated as a single, indivisible unit of work.&lt;/p&gt;

&lt;p&gt;While transactions are very common and often implicit in many Rails operations, in some cases mixing them with asynchronous actions might lead to unexpected results.&lt;/p&gt;

&lt;p&gt;Let's consider a practical example to illustrate potential issues, with a simple model and an asynchronous job (in our case, a Sidekiq job):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_save&lt;/span&gt; &lt;span class="ss"&gt;:do_something_asynchronous&lt;/span&gt;

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

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something_asynchronous&lt;/span&gt;
    &lt;span class="no"&gt;SyncUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SyncUser&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Some business logic on the user...&lt;/span&gt;

    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"ASYNC action on user with ID: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In normal circumstances, this code works as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rails console&lt;/span&gt;
user &lt;span class="o"&gt;=&lt;/span&gt; User.new&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s1"&gt;'John DOE'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
user.save
&lt;span class="c"&gt;# =&amp;gt; true&lt;/span&gt;

&lt;span class="c"&gt;# Sidekiq server&lt;/span&gt;
&lt;span class="c"&gt;## Performing SyncUserJob [...] from Sidekiq [...] with arguments: {:user_id=&amp;gt;1}&lt;/span&gt;
&lt;span class="c"&gt;## ASYNC action on user with name: John DOE&lt;/span&gt;
&lt;span class="c"&gt;## Performed SyncUserJob [...] in 37.25ms&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;However, let’s see what happens if a wrapping transaction prevents my &lt;code&gt;save&lt;/code&gt; call to be committed immediately to the DB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rails console&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'John DOE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

  &lt;span class="c1"&gt;# We wait for 5 sec to simulate a (very) long transaction&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Sidekiq server&lt;/span&gt;
&lt;span class="c1"&gt;# Performing SyncUserJob [...] from Sidekiq [...] with arguments: {:user_id=&amp;gt;2}&lt;/span&gt;
&lt;span class="c1"&gt;# Discarded SyncUserJob due to a ActiveRecord::RecordNotFound.&lt;/span&gt;
&lt;span class="c1"&gt;# Performed SyncUserJob [...] in 37.25ms&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We can see here that the job was performed in Sidekiq with an &lt;code&gt;ActiveRecord::RecordNotFound&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;This is due to the fact that, at the time of executing the job’s logic (in the Sidekiq server’s runtime), the DB operation hasn’t been committed yet (in the Rails app’s runtime), meaning the object does not exist in the DB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-atomic interactions in transactions
&lt;/h2&gt;

&lt;p&gt;Non-atomic interactions occur when asynchronous actions are triggered within a transaction that hasn't been committed yet. This situation can lead to race conditions on the executed async job due to the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The transaction can potentially rollback, causing &lt;strong&gt;data inconsistencies&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Even in successful operations, the transaction might take longer than expected to commit due to additional tasks being executed, leading to unexpected behaviours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a Rails environment, a common source of &lt;strong&gt;implicit&lt;/strong&gt; non-atomic interactions is ActiveRecord's native &lt;code&gt;after_save&lt;/code&gt; callback, which wraps its content in a transaction and runs side actions regardless of the transaction's outcome.&lt;/p&gt;

&lt;p&gt;While identifying them manually can sometimes be cumbersome, fortunately a great tool has been developed for this exact purpose...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Isolator gem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/palkan/isolator" rel="noopener noreferrer"&gt;Isolator&lt;/a&gt; is a great 'plug-n-play' gem designed to detect non-atomic interactions within database transactions automatically.&lt;/p&gt;

&lt;p&gt;It raises an error every time it detects such an interaction, helping you identify and address these issues as early as possible in your development process.&lt;/p&gt;

&lt;p&gt;It supports multiple adapters to catch operations in different contexts that are at risk of non-atomic interactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Isolator locally
&lt;/h2&gt;

&lt;p&gt;The gem needs little to no configuration to work, depending on your context.&lt;/p&gt;

&lt;p&gt;In our case, let’s Install the gem, and add minimal config:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'isolator'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# initializers/isolator.rb&lt;/span&gt;

&lt;span class="no"&gt;Isolator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Specify a custom logger to log offenses&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;

  &lt;span class="c1"&gt;# Raise exception on offense&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_exceptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# true in test env&lt;/span&gt;

  &lt;span class="c1"&gt;# Send notifications to uniform_notifier&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# Customize backtrace filtering (provide a callable)&lt;/span&gt;
  &lt;span class="c1"&gt;# By default, just takes the top-5 lines&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;backtrace_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backtrace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;backtrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Define a custom ignorer class (must implement .prepare)&lt;/span&gt;
  &lt;span class="c1"&gt;# uses a row number based list from the .isolator_todo.yml file&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignorer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Isolator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Ignorer&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s now re-run the same example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rails console&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'John DOE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;

  &lt;span class="c1"&gt;# We wait for 5 sec to simulate a (very) long transaction&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Isolator::BackgroundJobError: You are trying to enqueue background job inside db transaction. In case of transaction failure, this may lead to data inconsistency and unexpected bugs&lt;/span&gt;
&lt;span class="c1"&gt;# Details: SyncUserJob ({:user_id=&amp;gt;2})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now get an &lt;code&gt;Isolator::BackgroundJobError&lt;/code&gt; error that prevents &lt;code&gt;SyncUserJob&lt;/code&gt; from being enqueued, hence protecting data consistency.&lt;/p&gt;

&lt;p&gt;In this specific case, several simple solutions exist to prevent this behaviour:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;transactional callback&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Transactional callbacks like &lt;code&gt;after_commit&lt;/code&gt; or &lt;code&gt;after_rollback&lt;/code&gt; are native Rails tools that work the same as the standard callbacks, except that they don’t execute until after database changes have either been committed or rolled back.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Avoid the use of callbacks for such sequential actions, and favour an &lt;em&gt;interaction&lt;/em&gt; (as in the Interaction Pattern) or other kinds of service objects to encapsulate and sequentially run the logic of user creation followed by its job scheduling.&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;While the aforementioned example describes a simple case of non-atomic interaction wrapped in a transaction, real-world applications can obviously involve far more complex scenarios, where identifying these issues manually can be very tricky.&lt;/p&gt;

&lt;p&gt;Although it is important to note that it won’t fix all your issues, &lt;a href="https://github.com/palkan/isolator" rel="noopener noreferrer"&gt;Isolator&lt;/a&gt; is a great tool to help with such concerns. It is recommended for use in both test and development environments, preferably in a ‘whiny’ mode that raises errors when detecting dangerous operations. While it can be plugged into staging environments, extra careful consideration should be given to this approach.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>learning</category>
      <category>database</category>
    </item>
    <item>
      <title>Streamlining Rails Controllers with Simple PORO Validators</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Fri, 08 Nov 2024 11:50:10 +0000</pubDate>
      <link>https://dev.to/lcsm0n/streamlining-rails-controllers-with-simple-poro-validators-4h9a</link>
      <guid>https://dev.to/lcsm0n/streamlining-rails-controllers-with-simple-poro-validators-4h9a</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer: This article proposes a straightforward approach to request validation in Rails controllers. While it doesn't claim to be a perfect solution for every scenario, it presents a simple pattern that can be applied to many cases and help simplify controller responsibilities.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Observation
&lt;/h2&gt;

&lt;p&gt;When dealing with complex Rails controllers, finding an elegant and maintainable way to perform validations on incoming requests can be challenging, be it to simply check the presence of mandatory attributes or to deal with more intricate business-logic checks.&lt;/p&gt;

&lt;p&gt;Let’s consider this basic controller as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform_computation&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyBusinessLogicService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;param1: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;param2: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;param3: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It defines a simple action that calls a business-logic service and returns the result.&lt;/p&gt;

&lt;p&gt;Now, suppose we need to perform various validations, of different natures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check that &lt;code&gt;param1&lt;/code&gt; and &lt;code&gt;param2&lt;/code&gt; are present&lt;/li&gt;
&lt;li&gt;Check that the current user has the permission to perform this action, depending on its role (RBAC)&lt;/li&gt;
&lt;li&gt;Check that the current user has an attribute &lt;code&gt;active&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given the nature of the validations at stake here (input-related validation, session-related validation, and business-logic validation), we are likely to come to the conclusion that the controller is not the ideal place to perform them.&lt;/p&gt;

&lt;p&gt;The controller's primary role should focus on instantiating/updating/deleting objects, invoking services or libraries containing business logic, and formatting output data before rendering.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: In real-life cases, parameter validation and authorisation checks are likely to be split and/or factored out of the controllers. I am including them in the same validator here for the sake of the example, by lack of more business logic checks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The validator pattern
&lt;/h2&gt;

&lt;p&gt;Instead of cluttering the controller action with validation-related code, let’s define the following ‘validator’ class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RequestValidator&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnauthorizedError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InactiveUserError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="vi"&gt;@param1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;
    &lt;span class="vi"&gt;@param2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;
    &lt;span class="vi"&gt;@param3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;param3&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call!&lt;/span&gt;
    &lt;span class="n"&gt;validate_required_params!&lt;/span&gt;
    &lt;span class="n"&gt;validate_user_role!&lt;/span&gt;
    &lt;span class="n"&gt;validate_user_is_active!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_required_params!&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ParameterMissing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:param1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@param1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ParameterMissing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:param2&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@param2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_user_role!&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;UnauthorizEderror&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'User not allowed to perform this action'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;RoleChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;allowed?&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_user_is_active!&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;InactiveUserError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"User is not active"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active?&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of having a bloated controller, we can now plug validations in a very light fashion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform_computation&lt;/span&gt;
    &lt;span class="n"&gt;validate_computational_request!&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyBusinessLogicService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;param1: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;param2: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;param3: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;RequestValidator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UnauthorizedError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :unauthorized&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;RequestValidator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;InactiveUserError&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="c1"&gt;# Some custom logic&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_computational_request!&lt;/span&gt;
      &lt;span class="no"&gt;RequestValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;param1: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;param2: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;param3: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:param3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call!&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Principle / Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PORO Object&lt;/strong&gt;: The validator is implemented as a Plain Old Ruby Object (PORO), avoiding dependencies and complex designs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility&lt;/strong&gt;: Each validator object has a single responsibility and a single instance method (&lt;code&gt;call!&lt;/code&gt;), adhering to the Single Responsibility Principle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns&lt;/strong&gt;: The validation logic is clearly separated from the business logic, improving maintainability and readability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Straightforward Error Handling&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Sequential validations allow each step to raise specific errors with more informative context, aiding in debugging.&lt;/li&gt;
&lt;li&gt;Namespaced errors enable targeted exception handling, whether in individual actions or parent controllers. This can help logging relevant information prior to rendering custom messages.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Modularity and Reusability&lt;/strong&gt;: Validator classes can be easily reused across different controllers or actions, promoting code reuse and consistency.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Improved Testability&lt;/strong&gt;: With validations isolated in separate objects, unit testing becomes more focused and efficient.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Reduced Controller Complexity&lt;/strong&gt;: By moving validations out of controllers into dedicated objects, we achieve thinner, more manageable controllers.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This approach to request validation using simple PORO objects offers a clean, maintainable way to handle complex validation scenarios in Rails applications, leading to more organised and scalable codebases.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>cleancode</category>
      <category>learning</category>
    </item>
    <item>
      <title>Ruby exception Handling Pitfall: Understanding Rescue Clause Hierarchy</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Wed, 30 Oct 2024 17:17:25 +0000</pubDate>
      <link>https://dev.to/lcsm0n/ruby-exception-handling-pitfall-understanding-rescue-clause-hierarchy-cjo</link>
      <guid>https://dev.to/lcsm0n/ruby-exception-handling-pitfall-understanding-rescue-clause-hierarchy-cjo</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Let's consider the following code, defining basic classes and one custom error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomError&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;raise_custom_error&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;CustomError&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"StandardError rescued"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Parent&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;raise_custom_error&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;CustomError&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"CustomError rescued"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's examine the following method call::&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To summarize, this code does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a &lt;code&gt;CustomError&lt;/code&gt; class that inherits from &lt;code&gt;StandardError&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creates a &lt;code&gt;Child&lt;/code&gt; class with a &lt;code&gt;#call&lt;/code&gt; method that invokes  &lt;code&gt;raise_custom_error&lt;/code&gt; from its parent class &lt;code&gt;Parent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Implements exception handling in both &lt;code&gt;Child#call&lt;/code&gt; and &lt;code&gt;Parent#raise_custom_error&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given this setup, we might expect the exception handling mechanism in &lt;code&gt;Child#call&lt;/code&gt; to be triggered, as it appears to be the most specific. However, the actual output of the call is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;StandardError&lt;/span&gt; &lt;span class="n"&gt;rescued&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surprisingly, we end up executing the more generic error handling defined in &lt;code&gt;Parent#raise_custom_error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  How Rescuing Works
&lt;/h2&gt;

&lt;p&gt;When Ruby encounters a &lt;code&gt;raise&lt;/code&gt; statement, it &lt;strong&gt;looks up the call stack&lt;/strong&gt; for matching &lt;code&gt;rescue&lt;/code&gt; clauses. It checks whether the raised exception is a subclass of any exception specified in the &lt;code&gt;rescue&lt;/code&gt; clause.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Explanation of the behaviour
&lt;/h2&gt;

&lt;p&gt;The output "StandardError rescued" occurs because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When &lt;code&gt;Parent#raise_custom_error&lt;/code&gt; raises &lt;code&gt;CustomError&lt;/code&gt;, Ruby looks for matching rescue clauses in the method where the exception was raised&lt;/li&gt;
&lt;li&gt;It finds the &lt;code&gt;rescue StandardError&lt;/code&gt; clause in &lt;code&gt;Parent#raise_custom_error&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Since &lt;code&gt;CustomError&lt;/code&gt; inherits from &lt;code&gt;StandardError&lt;/code&gt;, this &lt;code&gt;rescue&lt;/code&gt; clause matches and handles the exception&lt;/li&gt;
&lt;li&gt;By the time execution returns to &lt;code&gt;Child#call&lt;/code&gt;, the exception has already been handled, so the rescue &lt;code&gt;CustomError&lt;/code&gt; clause in &lt;code&gt;Child#call&lt;/code&gt; never gets a chance to execute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;

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

&lt;p&gt;Error-rescuing blocks cannot be written completely agnostically of the current class hierarchy. The order and specificity of &lt;code&gt;rescue&lt;/code&gt; clauses matters due to exception inheritance.&lt;br&gt;
When dealing with exceptions raised in parent classes, &lt;strong&gt;the rescue block in the parent class may catch exceptions before they reach the child class&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To avoid such scenarios, we can apply a principle related to exception hierarchy, which is closely tied to the Liskov Substitution Principle (LSP). The LSP states that subtypes should be substitutable for their base types, meaning that any code using a parent class should be able to work with a child class without knowing the difference.&lt;/p&gt;

&lt;p&gt;In the context of exception handling specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More general exceptions should be caught at lower levels in the call stack or inheritance hierarchy.&lt;/li&gt;
&lt;li&gt;More specific exceptions should be caught closer to where they are raised.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>learning</category>
    </item>
    <item>
      <title>Adding a Rubocop config to an old repository | step-by-step guide</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Wed, 23 Oct 2024 07:59:37 +0000</pubDate>
      <link>https://dev.to/lcsm0n/adding-a-rubocop-config-to-an-old-repository-step-by-step-guide-49db</link>
      <guid>https://dev.to/lcsm0n/adding-a-rubocop-config-to-an-old-repository-step-by-step-guide-49db</guid>
      <description>&lt;p&gt;While using Rubocop as a linter/formatter is a no-brainer for most Rails developers these days, it wasn’t always the case. This means that there are probably tons of repositories of applications or gems out there still not benefitting from any linter configuration, and solving this issue might sometimes be cumbersome, especially on old code.&lt;/p&gt;

&lt;p&gt;Here’s a little step-by-step guide on how to do it, based on a recent update I made on an open-source lib I was working on, on which some files are more than 10 years old. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: this guide assumes that the code under scrutiny has a proper test suite, which is a requirement for serene code-refactoring of any nature.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Install Rubocop &amp;amp; relevant extensions
&lt;/h2&gt;

&lt;p&gt;First, I recommend installing the Rubocop gem and a few extensions, by adding them to your Gemfile and running bundle install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rubocop (required)
rubocop-performance
rubocop-rails
rubocop-rspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one of these extensions comes with a specific set of cops (=linting rules) addressing specific needs. Check out their respective documentations on the &lt;a href="https://docs.rubocop.org/rubocop/1.67/extensions.html" rel="noopener noreferrer"&gt;Rubocop doc&lt;/a&gt;, and do not hesitate to add more of them if it suits your needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Check for existing .rubocop.yml config, or create one
&lt;/h2&gt;

&lt;p&gt;In my case, there was an existing Rubocop config in the form of a &lt;code&gt;.rubocop.yml&lt;/code&gt; file, that had not been updated for years and had never been enforced, meaning that most of the cops were out-of-date or just not respected.&lt;/p&gt;

&lt;p&gt;In this case, you can add minimal configuration by updating this file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inherit_from: .rubocop_todo.yml

# The behavior of RuboCop can be controlled via the .rubocop.yml
# configuration file. It makes it possible to enable/disable
# certain cops (checks) and to alter their behavior if they accept
# any parameters. The file can be placed either in your home
# directory or in some project directory.
#
# RuboCop will start looking for the configuration file in the directory
# where the inspected file is and continue its way up to the root directory.
#
# See https://docs.rubocop.org/rubocop/configuration

require:
  - rubocop-performance
  - rubocop-rake
  - rubocop-rspec

AllCops:
  TargetRubyVersion: 3.3 # To be changed to suit your needs
  NewCops: enable
  Exclude:
    - tmp/**/*
    - vendor/bundle/**/* # Recommended if you are using Github Actions
  DisplayCopNames: true

# … 
# Here comes the config specific to each of the cops. If starting from scratch, you shouldn’t have any.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If not already existing, you can simply create this file manually.&lt;/p&gt;

&lt;p&gt;You will then need to create an empty &lt;code&gt;.rubocop_todo.yml&lt;/code&gt; file at the root of your directory for this file to load properly. We will see in an upcoming step what to do with this file.&lt;/p&gt;

&lt;p&gt;You can now run &lt;code&gt;bundle exec rubocop&lt;/code&gt; in your terminal to check that the config file is correctly loaded. In my case, some errors occurred because of a few of the cops mentioned in the config being outdated. I simply followed the prompted instructions to fix these, and ended up with a clean config file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run &lt;code&gt;rubocop —autocorrect&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now that you have a proper Rubocop config, you can run the &lt;code&gt;rubocop&lt;/code&gt; command with the &lt;code&gt;--safe-auto-correct&lt;/code&gt; option in order for the gem to safely refactor every piece of code that doesn’t respect the imported list of cops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rubocop --safe-auto-correct

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: This command might result in a numerous amount of changes, hence I suggest committing your previous changes in the first place in order to be able to separate autocorrection changes from configuration ones in your VCS.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Fix Potential Broken Code
&lt;/h2&gt;

&lt;p&gt;Once the changes are made, ensure no logic has been broken. Run your test suite and inspect the results of the previous command by &lt;strong&gt;carefully reading the diff&lt;/strong&gt; before committing anything.&lt;/p&gt;

&lt;p&gt;If you need to disable a misbehaving cop, add it to the &lt;code&gt;.rubocop.yml&lt;/code&gt; file as follows, and re-run the autocorrect command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Style/MultilineBlockChain: # Replace with the cop to be deactivated
  Enabled: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once satisfied with the results and confident that the code will run, commit your changes before proceeding to the next step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Whitelist desired cops autocorrectable with rubocop -A
&lt;/h2&gt;

&lt;p&gt;After autocorrecting the safe offenses, you can either stop here or address the unsafe ones. Unsafe cop corrections are more likely to break your code but can sometimes be autocorrected by the gem. To retain only the ones that do not break any logic, follow these steps:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the following command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rubocop -A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run your test suite and identify the changes that broke it&lt;/li&gt;
&lt;li&gt;In Rubocop’s logs, find the precise cops responsible for the changes&lt;/li&gt;
&lt;li&gt;Add them to the list of ignored cops in the &lt;code&gt;.rubocop_todo.yml&lt;/code&gt; file by running:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rubocop --auto-gen-config --only &amp;lt;YourCopName&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Checkout the previously committed version of every file except &lt;code&gt;.rubocop_todo.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Repeat the steps until your test suite succeeds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inspect the results of the previous operation by carefully reading the diff before committing anything. Once satisfied with the results and confident that the code will run, commit your changes.&lt;/p&gt;

&lt;p&gt;You have now run unsafe autocorrection on your entire repository while excluding the non-autocorrectable cops by adding them to a ‘TODO’ file, where you can address them later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run rubocop --auto-gen-config to complete the .rubocop_todo.yml file
&lt;/h2&gt;

&lt;p&gt;At this stage, we have covered every autocorrection Rubocop can make. However, some offenses might remain. You can either fix them manually or temporarily append them to the &lt;code&gt;.rubocop_todo.yml&lt;/code&gt; file as a TODO list to handle later. The following command will add all remaining offenses’ associated cops to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rubocop --auto-gen-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now commit these changes as well, and your repository should subsequently be offence-free. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: Ignore the Resulting Revisions in Your git blame
&lt;/h2&gt;

&lt;p&gt;Running autocorrection commands can result in hundreds of lines of diff being committed, polluting the &lt;code&gt;git blame&lt;/code&gt; results for other contributors.&lt;br&gt;
To avoid this, use the &lt;code&gt;git --ignore-revs-file&lt;/code&gt; feature (documentation &lt;a href="https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt" rel="noopener noreferrer"&gt;here&lt;/a&gt;), which lists the revision numbers (or commit SHAs) to be ignored by git blame in a designated file, thus not taking them into account when displaying the future.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus #2: Add a CI step (recommended)
&lt;/h2&gt;

&lt;p&gt;The best way to avoid upcoming offenses is to add a CI task running the Rubocop command and preventing anyone from merging code that contains offenses.&lt;/p&gt;

&lt;p&gt;If you are using Github Actions, &lt;a href="https://github.com/marketplace/actions/rubocop-linter-action" rel="noopener noreferrer"&gt;basic actions&lt;/a&gt; exist to ensure this in the simplest way.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>codequality</category>
    </item>
    <item>
      <title>The importance of the environment in Regex pattern matching</title>
      <dc:creator>Lucas M.</dc:creator>
      <pubDate>Wed, 16 Oct 2024 11:49:34 +0000</pubDate>
      <link>https://dev.to/lcsm0n/the-importance-of-the-environment-in-regex-pattern-matching-3io2</link>
      <guid>https://dev.to/lcsm0n/the-importance-of-the-environment-in-regex-pattern-matching-3io2</guid>
      <description>&lt;p&gt;Here’s a small discovery I made regarding Ruby regex rules and whitespace characters, that made me scratch my head for a moment:&lt;/p&gt;

&lt;p&gt;Let’s have a look the following string, extracted from an email body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From :     John DOE &amp;lt;test@test.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that the space character between &lt;code&gt;'From'&lt;/code&gt; and the column (&lt;code&gt;‘:’&lt;/code&gt;) is a Non-Breaking Space Character (&lt;code&gt;U+00A0&lt;/code&gt; in Unicode), while the other spaces in this string are regular whitespaces (&lt;code&gt;U+0020&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Let’s now consider the following regex rule, defined in a Ruby constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REGEX = /(?:From)\s*:\s*(?:.*?&amp;lt;)?([^&amp;lt;&amp;gt;\s]+@[^&amp;gt;\s]+)(?:&amp;gt;)?/i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When testing the mentioned string against this regex in a Ruby console, &lt;strong&gt;we don’t get any match&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REGEX.match(‘From :  John DOE &amp;lt;test@test.com&amp;gt;’)
=&amp;gt; nil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why, you may wonder?&lt;/strong&gt;&lt;br&gt;
The reason for this is that the &lt;code&gt;\s&lt;/code&gt; matcher does not look for Non-Breaking Space Characters. In order to make it work, we need to update the regex to explicitly expect NBSC characters, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REGEX = /(?:From)[\s\u00A0]*:\s*(?:.*?&amp;lt;)?([^&amp;lt;&amp;gt;\s]+@[^&amp;gt;\s]+)(?:&amp;gt;)?/i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Everything looks fine up to that point.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However - here it becomes weird - when testing the original regex rule (the first one, without the &lt;code&gt;\u00A0&lt;/code&gt; part) on the same string in an interactive visualiser (&lt;a href="https://regexr.com/" rel="noopener noreferrer"&gt;https://regexr.com/&lt;/a&gt; for instance), &lt;strong&gt;there is a match&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c11o1iv7hygfzsgrvtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c11o1iv7hygfzsgrvtf.png" alt="Screenshot of the test made on Regexr" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My understanding of the situation is that the interactive Regex visualiser actually converts the NBSC to regular whitespace when copy-pasting the string into its text input, simply because the browser interprets it as a regular whitespace in its HTML rendering. &lt;/p&gt;

&lt;p&gt;This little experiment highlights the importance of testing regex patterns in the exact environment where they will be used. While online tools can be helpful for quick tests, they don't always accurately represent how the regex will behave in your production environment.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: It is worth mentioning that the string under scrutiny was copy-pasted from the original email at every stage of this experiment, meaning that the string itself wasn’t transformed by the copy-pasting operation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>regex</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
