<?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>Forem: Vlad Hilko</title>
    <description>The latest articles on Forem by Vlad Hilko (@vladhilko).</description>
    <link>https://forem.com/vladhilko</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%2F979672%2Fff62364d-3e07-4531-bfd6-f0ca495d12a3.jpg</url>
      <title>Forem: Vlad Hilko</title>
      <link>https://forem.com/vladhilko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vladhilko"/>
    <language>en</language>
    <item>
      <title>Introduction to Rails API: How to Create Your First Endpoint in Less Than a Minute?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Tue, 29 Aug 2023 21:53:19 +0000</pubDate>
      <link>https://forem.com/vladhilko/introduction-to-rails-api-how-to-create-your-first-endpoint-in-less-than-a-minute-4l66</link>
      <guid>https://forem.com/vladhilko/introduction-to-rails-api-how-to-create-your-first-endpoint-in-less-than-a-minute-4l66</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This is the first article in the Clean Rails API series. In this piece, we will provide a brief introduction to Rails API. We'll discuss what it is, how it looks, its key components, and share simple examples. We'll also demonstrate how request specs can be added. Furthermore, we'll provide a direction for API evolution and highlight the problems that have to be solved in the future to provide a high-quality solution.&lt;/p&gt;

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

&lt;p&gt;API (Application Programming Interface) is a set of rules and protocols that allow one application to interact with another application. There are several API paradigms that fall into two main categories: &lt;strong&gt;Request–Response APIs&lt;/strong&gt; and &lt;strong&gt;Event-Driven APIs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request–Response APIs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;REST API (Representational State Transfer)&lt;/li&gt;
&lt;li&gt;RPC (Remote Procedure Call)&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Event-Driven APIs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Polling&lt;/li&gt;
&lt;li&gt;WebHooks&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;HTTP Streaming&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In the context of this introduction to Rails API, we will be working with REST API. The name REST ("Representational State Transfer") encapsulates the essence of this architectural style: &lt;strong&gt;representing&lt;/strong&gt; resources and their &lt;strong&gt;states&lt;/strong&gt; and &lt;strong&gt;transferring&lt;/strong&gt; data between clients and servers using HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Let's review our plan for the Rails API introduction. We'll begin with the &lt;strong&gt;Primitive Implementation&lt;/strong&gt;. Following that, we will add several &lt;strong&gt;CRUD Endpoints&lt;/strong&gt; and cover them using &lt;strong&gt;RSpec Request Tests&lt;/strong&gt;. Finally, we will highlight areas for &lt;strong&gt;Improvements&lt;/strong&gt; that we'll consider in the next article of this series.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primitive Implementation
&lt;/h3&gt;

&lt;p&gt;At this part, we're going to add a very simple endpoint just to demonstrate how the API may look. We'll add this endpoint to the existing Rails application, but if you want to start from scratch, just create a new app with the &lt;code&gt;rails new api_sample --api&lt;/code&gt; command. We will start by providing the &lt;strong&gt;Desired Interface&lt;/strong&gt;, then we will add the &lt;strong&gt;Implementation&lt;/strong&gt;, and after that, &lt;strong&gt;check that everything works as expected&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Desired Interface
&lt;/h4&gt;

&lt;p&gt;First of all, let's decide how our interface should look like and what data we expect to receive. Let's assume that we want to send a GET request to &lt;code&gt;http://localhost:3000/api/hello-world&lt;/code&gt; and expect to receive the following response:&lt;/p&gt;

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

&lt;span class="c1"&gt;// Status: 200 OK&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Implementation
&lt;/h4&gt;

&lt;p&gt;To add such an API endpoint, we have to follow two simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new controller and method that will return &lt;code&gt;"data": "Hello World!"&lt;/code&gt; in JSON format.&lt;/li&gt;
&lt;li&gt;Add a new route to bind our HTTP request to the created controller method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To implement the first step, we will add a new &lt;code&gt;HelloWorldController&lt;/code&gt; with a &lt;code&gt;hello_world&lt;/code&gt; method inside:&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;# app/controllers/api/hello_world_controller.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorldController&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;hello_world&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;data: &lt;/span&gt;&lt;span class="s1"&gt;'Hello World!'&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;To implement the second step, we will add &lt;code&gt;get 'api/hello-world'&lt;/code&gt; to &lt;code&gt;config/routes.rb&lt;/code&gt;, which will bind the HTTP request and the controller method.&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;# config/routes.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'api/hello-world'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: :hello_world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s1"&gt;'api/hello_world'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Verify Functionality
&lt;/h4&gt;

&lt;p&gt;After completing our implementation, we can run the server and check its functionality:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

rails s


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

&lt;/div&gt;

&lt;p&gt;When you open your browser and visit &lt;code&gt;http://localhost:3000/api/hello-world&lt;/code&gt;, you should see the following:&lt;/p&gt;

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

&lt;span class="c1"&gt;// Status: 200 OK&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;That's it! The simplest API has been added. In the next chapter, we will try to add more HTTP requests to cover CRUD actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding CRUD Endpoints
&lt;/h3&gt;

&lt;p&gt;In this chapter, we are going to add new API endpoints for CRUD HTTP requests. We'll strive to keep everything as simple as possible, focusing solely on the core components. We will begin by defining the &lt;strong&gt;Desired Interface&lt;/strong&gt;, then proceed with the &lt;strong&gt;Implementation&lt;/strong&gt;, and finally, we'll &lt;strong&gt;verify that everything works as expected&lt;/strong&gt; and cover all endpoints with &lt;strong&gt;Request Specs&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Desired Interface
&lt;/h3&gt;

&lt;p&gt;We've demonstrated how to create the simplest API in the Hello World example. Let's go further and create more endpoints with different HTTP methods. I'd like to have the following 4 endpoints and expect them to return the following data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 200 OK&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 201 Created&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PUT http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 204 No Content&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DELETE http://localhost:3000/api/countries/1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 204 No Content&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;To add these 4 endpoints, we will need to follow the same 2 steps as in the previous part, plus one improvement. Let's start with the improvement. In our enhancement, we want to create a new wrapper class that we will use as the parent class for all our API controllers. This new class will look like this:&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;# app/controllers/api/application_controller.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API&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;Then, instead of inheriting from the &lt;code&gt;ApplicationController&lt;/code&gt; class, we'll have all API controllers inherit from &lt;code&gt;Api::ApplicationController&lt;/code&gt;. This change will provide us with more flexibility and reduce the risk of breaking non-API controllers if we have any.&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;# app/controllers/api/hello_world_controller.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloWorldController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&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;hello_world&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;data: &lt;/span&gt;&lt;span class="s1"&gt;'Hello World!'&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;Now let's create a new controller with 4 methods to handle all the requests mentioned above:&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;# app/controllers/api/countries_controller.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Api&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CountriesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&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;index&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="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Spain'&lt;/span&gt; &lt;span class="p"&gt;}&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;create&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;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Spain'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :created&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;update&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:no_content&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;destroy&lt;/span&gt;
      &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:no_content&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;And update the &lt;code&gt;routes.rb&lt;/code&gt; file to bind HTTP requests and controller methods:&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;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'api/countries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: :index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s1"&gt;'api/countries'&lt;/span&gt;
  &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'api/countries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: :create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s1"&gt;'api/countries'&lt;/span&gt;
  &lt;span class="n"&gt;put&lt;/span&gt; &lt;span class="s1"&gt;'api/countries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: :update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s1"&gt;'api/countries'&lt;/span&gt;
  &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="s1"&gt;'api/countries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: :destroy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s1"&gt;'api/countries'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Verify Functionality
&lt;/h3&gt;

&lt;p&gt;After everything has been implemented, let's check if it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Status: 200 OK&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The first one works as expected, and we know how to check it in the browser, but what about the others? How can we send different HTTP requests? To solve this problem, we're going to use Postman. Postman is like an extended browser version that can send any HTTP request based on your instructions. Postman looks like this:&lt;/p&gt;

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

&lt;p&gt;Let's check our other HTTP requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 201 Created&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Spain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PUT http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 204 No Content&lt;/span&gt;


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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DELETE http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;// Status: 204 No Content&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you can see, everything works as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Request Tests
&lt;/h3&gt;

&lt;p&gt;After all requests have been added, we need to cover them using request specs to ensure that everything will work as expected in the future as well. To add specs, we will use the &lt;a href="https://github.com/rspec/rspec-rails" rel="noopener noreferrer"&gt;rspec-rails gem&lt;/a&gt;. Our plan is to cover the 4 HTTP request methods that have been added above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# spec/requests/countries/list_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Listing Countries'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="n"&gt;request_uri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:request_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'/api/countries'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'lists all countries'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;send_request&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:successful&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Spain'&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;ul&gt;
&lt;li&gt;&lt;code&gt;POST http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# spec/requests/countries/create_spec.rb &lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Creating Countries'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="n"&gt;request_uri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:request_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'/api/countries'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'creates a country and returns it'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;send_request&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Spain'&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;ul&gt;
&lt;li&gt;&lt;code&gt;PUT http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# spec/requests/countries/update_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Updating Countries'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;put&lt;/span&gt; &lt;span class="n"&gt;request_uri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:request_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'/api/countries'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'updates the country and returns nothing'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;send_request&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:no_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&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;ul&gt;
&lt;li&gt;&lt;code&gt;DELETE http://localhost:3000/api/countries&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# spec/requests/countries/destroy_spec.rb &lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Destroying Countries'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;request_uri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:request_uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'/api/countries'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'deletes the country and returns nothing'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;send_request&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:no_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&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;That's it. You can run &lt;code&gt;rspec spec/requests&lt;/code&gt; to ensure that they work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;We have demonstrated the simplest examples, but there's room for many improvements. I would consider at least the following points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update Routing&lt;/li&gt;
&lt;li&gt;Add Representers/Serializers&lt;/li&gt;
&lt;li&gt;Add logic to Create/Update/Delete resources&lt;/li&gt;
&lt;li&gt;Add Error Handling&lt;/li&gt;
&lt;li&gt;Add Request Tests/RSwag Documentation&lt;/li&gt;
&lt;li&gt;Apply Authorization/Authentication rules&lt;/li&gt;
&lt;li&gt;Support URL queries (limits/pagination/filtering)&lt;/li&gt;
&lt;li&gt;Set up Middleware (Rate Limits/CORS/etc...)&lt;/li&gt;
&lt;li&gt;Add API Versioning&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;To sum it up, this article is the beginning of our Clean Rails API series. We've talked about what Rails API is, shown some examples, and explained how to test it. In the future, we will improve our solution by addressing each improvement point one by one.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>restapi</category>
    </item>
    <item>
      <title>How to Conduct High-Quality Code Review?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Sat, 26 Aug 2023 10:28:27 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-conduct-high-quality-code-review-2loo</link>
      <guid>https://forem.com/vladhilko/how-to-conduct-high-quality-code-review-2loo</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to declare the structure of a code review process that will help you build a product with better code quality in less time. We will begin by explaining the main reasons for code reviews and the benefits they can provide to your team. Next, we will identify the key participants in code reviews, their responsibilities, and the workflow. After that, we will delve into a detailed discussion of the plans for the organization, code authors, and reviewers that will help them get the most out of code reviews and achieve excellent results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we need code review?
&lt;/h2&gt;

&lt;p&gt;Code review is a process where someone other than the author(s) of a piece of code examines that code. It is mostly used for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimizing mistakes and their impacts:&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This includes making sure bugs and defects are prevented as much as possible and that the source code is of high quality.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Maintaining norms:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This involves ensuring that there are adequate tests, consistency in style, design, and implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Meeting requirements:&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on Acceptance Criteria.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Improving code quality/performance/security.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learning and sharing:&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Both domain and coding knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tracing and tracking decisions:&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Understanding the evolution of the code and why and how changes have happened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gatekeeping:&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Ensuring security and having an additional safety net so that a single developer cannot commit arbitrary code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Finding Alternative Solutions.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Structure Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Actors
&lt;/h3&gt;

&lt;p&gt;First of all, let's define the main actors of code review. I would choose the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Organization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Author&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core Reviewer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Responsibilities
&lt;/h3&gt;

&lt;p&gt;Let's define their responsibilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The organization's responsibility is to simplify life for Code Authors and Reviewers by defining rules and processes. Every question related to the code review process should have answers provided by the organization. Here are examples of questions that can be raised and should have answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to encourage team members to participate in code reviews?&lt;/li&gt;
&lt;li&gt;How to resolve conflicts?&lt;/li&gt;
&lt;li&gt;How to determine when a review is complete and it's okay to approve?&lt;/li&gt;
&lt;li&gt;How to encourage team members to follow the process?&lt;/li&gt;
&lt;li&gt;What to do if you have a strict deadline or need to merge a critical bug fix as soon as possible?&lt;/li&gt;
&lt;li&gt;How to respond when someone says 'No' during the review?&lt;/li&gt;
&lt;li&gt;Who will perform code reviews?&lt;/li&gt;
&lt;li&gt;How often should code reviews take place?&lt;/li&gt;
&lt;li&gt;Who is responsible for assigning tickets/code reviews to relevant engineers?&lt;/li&gt;
&lt;li&gt;What should you do if no one wants to review your PR?&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Code Author's main responsibility is to implement a high-quality solution and to simplify the life of the reviewer as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Reviewer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Code Reviewer's main responsibility is to double-check the author's work and identify improvements that the author may not have noticed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flows
&lt;/h3&gt;

&lt;p&gt;Let's define the flow for each of them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Organization should create a document with answers to all possible questions that may arise and ensure that teams adhere to the established rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Author&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are 5 points that every Code Author should follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a Pull Request (PR) and describe the changes to provide more context for reviewers.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perform a self-check and review your code before submitting it.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find the best reviewer.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborate with the Reviewer to resolve all issues and obtain approval.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow the organization's process to merge the changes.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Code Reviewer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are 5 points that every Code Reviewer should check before giving approval:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ensure that the code follows business requirements.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure that complexity has been reduced.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure that the design is good.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure that reliability is at the appropriate level.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check that the code doesn't have any hidden risks.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In addition, the reviewer should know how to collaborate with the author to resolve all issues.&lt;/p&gt;

&lt;p&gt;Let's discuss these responsibilities and flows for each actor more precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Standard of Code Review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Organization-Wide Practices
&lt;/h3&gt;

&lt;p&gt;During code reviews, many issues can arise, and we need to know how to handle them. To address these problems and ensure that everyone is on the same page and at the same position, the organization should create a document outlining engineering practices and rules to document and standardize the code review process. This document should cover the following main points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that the purpose of code reviews is clear to everyone.&lt;/li&gt;
&lt;li&gt;Ensure that the "Definition of Done" is documented and clear to everyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an essential point because we need to establish boundaries. We can't attain perfection, so we need to define minimum rules required for a PR to be considered good enough. For example, the following criteria could be considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test coverage is sufficient and correct; all border cases are covered.&lt;/li&gt;
&lt;li&gt;CI is green, and all tests have passed.&lt;/li&gt;
&lt;li&gt;Rubocop and other style linters have passed.&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Encourage team members to actively participate in code reviews.&lt;/li&gt;
&lt;li&gt;Define a process for conflict resolution during code reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;P.S. You can read Google's process for conflict resolution here: &lt;a href="https://github.com/google/eng-practices/blob/master/review/reviewer/standard.md#resolving-conflicts-conflicts"&gt;Resolving Conflicts&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Set clear expectations for code review turnaround times.&lt;/li&gt;
&lt;li&gt;Prioritize code reviews as an essential task.&lt;/li&gt;
&lt;li&gt;Leverage code reviews as opportunities for knowledge sharing and learning.&lt;/li&gt;
&lt;li&gt;Encourage reviewing code in unfamiliar areas to promote cross-functional knowledge.&lt;/li&gt;
&lt;li&gt;Continuously monitor and improve the code review process.&lt;/li&gt;
&lt;li&gt;Recognize and reward those with a track record of providing quality feedback.&lt;/li&gt;
&lt;li&gt;Conduct regular code review sessions to discuss broader trends or issues that arise during the review process.&lt;/li&gt;
&lt;li&gt;Encourage authors to seek feedback during development before submitting a formal code review.&lt;/li&gt;
&lt;li&gt;Ensure that you have proper processes in place to speed up reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;P.S. Learn more about how Google accelerates reviews here: &lt;a href="https://github.com/google/eng-practices/blob/master/review/reviewer/speed.md"&gt;Fast Review&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Determine how to handle emergencies&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;P.S. Learn more about how Google handles emergencies here: &lt;a href="https://github.com/google/eng-practices/blob/master/review/emergencies.md"&gt;Emergencies&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the full document from the Google team outlining their practices: &lt;a href="https://github.com/google/eng-practices"&gt;Google Engineering Practices Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Author
&lt;/h2&gt;

&lt;p&gt;Your goal as an author of code seeking review is to simplify the reviewer's life as much as possible. You are the one in need of the review and who should receive the review, not the reviewer who wishes to spend their time reviewing your code. Therefore, ensure that you do not compel them to perform tasks that you could handle yourself.&lt;/p&gt;

&lt;p&gt;There are 5 steps that every Code Author should follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create a Pull Request (PR) and describe the changes to provide more context for reviewers.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perform a self-check and review your code before submitting it.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Find the best reviewer.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborate with the Reviewer to resolve all issues and obtain approval.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow the organization's process to merge the changes.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Providing Context for Reviewers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ensure that you include a proper title, description, any screenshots, relevant links, configuration changes, etc., in the PR.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;P.S. You can read Google's requirements for the description here: &lt;a href="https://github.com/google/eng-practices/blob/master/review/developer/cl-descriptions.md"&gt;Google PR Description Guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Make sure to add a very clear reason for why you're making these changes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Achieving code perfection is challenging because every code change introduces a risk of breaking something. Without code, there's no risk. Therefore, it's crucial to ensure that there are no alternative solutions to the problem. If you only describe the code changes without providing the reason, you're depriving the reviewer of the opportunity to suggest a completely different solution to the problem. While the reason may be clear to you, keep in mind that reviewers may not have your context, so it's essential to add it.&lt;/p&gt;

&lt;p&gt;Even if you and your reviewers currently understand the reason, consider adding it for historical purposes. In a few years, you may forget the initial rationale, making it much harder to replace or refactor the solution. For example, if someone later introduces a new global solution that could solve the problem more efficiently, they might struggle to replace the code because they expected your code to perform additional functions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Take notes on any questions or concerns about the change to discuss them during the review.&lt;/li&gt;
&lt;li&gt;Identify any potential performance, security, or scalability concerns and note them for discussion during the review.&lt;/li&gt;
&lt;li&gt;Avoid attempting to sneak something past reviewers; highlight questionable elements instead.&lt;/li&gt;
&lt;li&gt;Consider the impact of the change on other parts of the system and express your concerns to others.&lt;/li&gt;
&lt;li&gt;Provide context for your design choices by explaining why you chose a particular approach.&lt;/li&gt;
&lt;li&gt;If there are any alternative solutions that were considered and why they were not chosen, briefly mention them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Self-Check: Review Your Code Before Submitting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Before submitting a PR, thoroughly test your changes locally to identify and address any obvious issues.&lt;/li&gt;
&lt;li&gt;Write automated/unit tests to validate the functionality.&lt;/li&gt;
&lt;li&gt;Double-check for spelling mistakes.&lt;/li&gt;
&lt;li&gt;Perform code cleanup, removing comments, todos, and unnecessary artifacts.&lt;/li&gt;
&lt;li&gt;Confirm that the code passes all tests, including unit, integration, and system tests.&lt;/li&gt;
&lt;li&gt;Review the code to ensure it aligns with the project's coding standards, team guidelines, and best practices.&lt;/li&gt;
&lt;li&gt;Maintain consistency with the overall project design and architecture.&lt;/li&gt;
&lt;li&gt;Write or update documentation for the changes if required.&lt;/li&gt;
&lt;li&gt;Keep your code changes concise and focused. Avoid overwhelming reviewers with extensive code in a single review session.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;It has been proven that we get more value from code reviews when the PR is smaller rather than big. If you are struggling to keep them small, try pairing with a teammate (or engineers from other teams).&lt;/p&gt;

&lt;p&gt;P.S. You can read about why Google prefers to keep PRs small here: &lt;a href="https://github.com/google/eng-practices/blob/master/review/developer/small-cls.md"&gt;Google Engineering Practices&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Finding the Best Reviewer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who to Choose:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone capable of responding to your review within a reasonable period of time.&lt;/li&gt;
&lt;li&gt;Someone who can provide the most thorough and accurate review for the piece of code you are writing.&lt;/li&gt;
&lt;li&gt;Look for reviewers who have expertise in the domain or specific functionality related to your code changes.&lt;/li&gt;
&lt;li&gt;Reviewers who have been involved in previous discussions or decisions related to the code you are changing may have valuable historical context.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Tools to find:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the codeowners file.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;git blame&lt;/code&gt; to identify potential reviewers.&lt;/li&gt;
&lt;li&gt;Document which teams are responsible for specific areas to facilitate reviewer selection.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Collaboration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Approach the review process with an open mind and be willing to learn from and collaborate with other team members.&lt;/li&gt;
&lt;li&gt;Address all the feedback received, including any concerns or questions raised.&lt;/li&gt;
&lt;li&gt;Don't dismiss or resolve a comment without explanation. Every comment is valid; as a code author, even if you feel the comment isn't relevant, please address it so the reviewer can understand the context and why you've done something in a certain way.&lt;/li&gt;
&lt;li&gt;Seek feedback from other team members if you are unsure about the changes.&lt;/li&gt;
&lt;li&gt;If you're unsure about a review comment or suggestion, don't hesitate to ask for clarification.&lt;/li&gt;
&lt;li&gt;Don't take it personally. It's the code under review, not you.&lt;/li&gt;
&lt;li&gt;View each review as a chance to learn and grow as a developer. Embrace feedback as a path to improvement.&lt;/li&gt;
&lt;li&gt;Be open to new suggested approaches, even if you consider them not too important; it's a good opportunity to learn a new way of doing things.&lt;/li&gt;
&lt;li&gt;Do not make every change that is suggested if you still believe your approach is better. Have a conversation and agree with the reviewer if possible.&lt;/li&gt;
&lt;li&gt;Treat everyone's opinion the same, regardless of seniority. Ensure you don't apply a change if someone senior told you to do so without understanding the reasons.&lt;/li&gt;
&lt;li&gt;Take ownership of your code and its quality. Be accountable for the changes you make.&lt;/li&gt;
&lt;li&gt;Ensure that all code review discussions, comments, and decisions are documented within the code review platform. This creates a historical record and helps other team members understand the rationale behind the code changes.&lt;/li&gt;
&lt;li&gt;Create a positive and collaborative atmosphere by expressing your gratitude to the reviewer&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Good: 'Thank you!'&lt;/p&gt;

&lt;p&gt;Good: 'Nice catch!'&lt;/p&gt;

&lt;p&gt;Good: 'Thanks for pointing that out'&lt;/p&gt;

&lt;p&gt;Good: 'Thanks for taking the time to review'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  After Review
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Express Gratitude&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Code reviews are, in large part, about having others watch your back. Don't hesitate to express your gratitude with a simple "Thank you" once the review is completed. If you're new to code reviews, take a moment to reflect on what went well and what didn't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approval Checks:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that you have received enough approvals for your code changes.&lt;/li&gt;
&lt;li&gt;If you've made changes to unfamiliar areas, ensure you've received approval from the code owner.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing and QA:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that your ticket will be tested by Quality Assurance (QA).&lt;/li&gt;
&lt;li&gt;Provide clear and comprehensive descriptions of all places you've modified in the ticket description.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Integration (CI):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the CI pipeline has successfully passed for your code changes.&lt;/li&gt;
&lt;li&gt;Confirm that the main branch's CI pipeline is not failing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm that it's an appropriate time to merge your changes:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Not too late or too early to ensure support if issues arise.&lt;/li&gt;
&lt;li&gt;Not during holidays&lt;/li&gt;
&lt;li&gt;Ensure that you do not merge code during the process of fixing critical issues to avoid additional problems.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Merge the approved code changes into the main branch.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-Merge Checks:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that the main branch's CI pipeline remains green after merging your changes.&lt;/li&gt;
&lt;li&gt;Verify that the code changes are functioning as expected in the production environment.&lt;/li&gt;
&lt;li&gt;Keep an eye on monitoring tools to promptly address any issues that may arise due to your code changes.&lt;/li&gt;
&lt;li&gt;Review and update documentation, such as README files, API documentation, or user guides, to reflect any changes made in the code.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Celebrate&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reviewer
&lt;/h2&gt;

&lt;p&gt;The main goal and priority of a review are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reduce the risk of breaking existing logic and introducing new bugs.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid complicating the lives of other programmers in the future.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can split the reviewer's task into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard Skills (What Do Code Reviewers Look For?):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Meeting business requirements (based on Acceptance Criteria)&lt;/li&gt;
&lt;li&gt;Reduce Complexity(Style, Consistency, Readability)&lt;/li&gt;
&lt;li&gt;Improve Design (Maintainability/Testability/Stability/Reusability)&lt;/li&gt;
&lt;li&gt;Improve Reliability (Performance/Security)&lt;/li&gt;
&lt;li&gt;Mitigate Risks&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft Skills (How should Code Reviewers behave to address the problems they find?):&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Collaboration&lt;/li&gt;
&lt;li&gt;Respectful Comments&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if your code doesn't break anything and simplifies the lives of other developers, it's okay to approve it. Below, we will discuss the main points on how to achieve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard Skills (What Do Code Reviewers Look For?)
&lt;/h2&gt;

&lt;p&gt;Code reviewers should consider the following points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Meeting business requirements&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduce Complexity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve Design&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve Reliability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitigate Risks&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Meeting business requirements
&lt;/h3&gt;

&lt;p&gt;If the author hasn't implemented what should be done according to the business requirements, then all other reviews will be a waste of time. So, your priority here is to identify this issue as soon as possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Review any documentation or design specifications related to the change (JIRA story, design document, etc.) and compare them with the author's description of the changes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Based on the requirements, prepare a list of items that should have been covered in the changes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Functionality Based on Acceptance Criteria&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Do we really need it? Does the change make sense? Does it have a good description?&lt;/li&gt;
&lt;li&gt;What problem do we want to solve?&lt;/li&gt;
&lt;li&gt;Is the business logic correct?&lt;/li&gt;
&lt;li&gt;Are the Acceptance Criteria satisfied?&lt;/li&gt;
&lt;li&gt;Are there any potential edge cases or scenarios not covered by the acceptance criteria that should be considered?&lt;/li&gt;
&lt;li&gt;Does the code behave as the author likely intended?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Usability and User Experience&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Are error messages clear and user-friendly?&lt;/li&gt;
&lt;li&gt;Does the UI gracefully handle unexpected errors or input?&lt;/li&gt;
&lt;li&gt;Does the UI support multiple languages and locales?&lt;/li&gt;
&lt;li&gt;Does the UI provide a good user experience on mobile devices?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reduce Complexity
&lt;/h3&gt;

&lt;p&gt;If you can't understand the code, it's very likely that other developers won't either. So you're also helping future developers understand this code when you ask the developer to clarify it.&lt;/p&gt;

&lt;p&gt;"Too complex" usually means "can't be understood quickly by code readers." It can also mean "developers are likely to introduce bugs when they try to call or modify this code."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Could the code be made simpler? &lt;/li&gt;
&lt;li&gt;Can the solution be simplified?&lt;/li&gt;
&lt;li&gt;Does similar functionality already exist in the codebase? If yes, why isn’t it reused?&lt;/li&gt;
&lt;li&gt;Is the PR more complex than it should be? Could it be split into parts?&lt;/li&gt;
&lt;li&gt;Could some parts be moved to another ticket?&lt;/li&gt;
&lt;li&gt;Are there redundant or outdated comments? Is there any commented-out code?&lt;/li&gt;
&lt;li&gt;Is the developer not implementing things they might need in the future but don't need now?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Would another developer be able to easily understand and use this code when they come across it in the future?&lt;/li&gt;
&lt;li&gt;Does your code tell the story clearly? Or does it clearly explain the business flow?&lt;/li&gt;
&lt;li&gt;Did the developer choose clear names for variables, classes, methods, etc.?&lt;/li&gt;
&lt;li&gt;Are there excessive abbreviations or cryptic variable names that could be made more descriptive?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Style&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Does the code follow the company-defined style guides?&lt;/li&gt;
&lt;li&gt;Is code formatting consistent, including indentation, spacing, and line breaks?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Is there a consistent approach to coding within your company's rules?&lt;/li&gt;
&lt;li&gt;Does the code change adhere to the project's coding standards and best practices?&lt;/li&gt;
&lt;li&gt;Are naming conventions consistent with the project's coding standards?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Improve Design
&lt;/h3&gt;

&lt;p&gt;In fact, reviewing the rest of the PR might be a waste of time because if the design problems are significant enough, a lot of the other code under review is going to disappear and not matter anyway.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Is the code well-designed and appropriate for your system?&lt;/li&gt;
&lt;li&gt;Are there any best practices, design patterns, or language-specific patterns that could substantially improve this code?&lt;/li&gt;
&lt;li&gt;Can we improve the code via design patterns?&lt;/li&gt;
&lt;li&gt;Does this code adhere to Design Principles? (KISS, SOLID, YAGNI, etc.)&lt;/li&gt;
&lt;li&gt;Have you found any code smells in the code?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is the code well-designed?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Will it be easy to change the code for other developers in the future?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are architectural or major design changes approved by a Principal or an Architect?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are you sure that no new unit/gem/architecture/etc. is introduced into the codebase without the prior approval process (tech leadership meeting) or at least being discussed with a Principal?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Improve Reliability
&lt;/h3&gt;

&lt;p&gt;This step allows you to identify any potential performance, security, or scalability concerns and discuss them with the author.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perfomance&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Do you think this code change will negatively impact system performance?&lt;/li&gt;
&lt;li&gt;Do you see potential for significant code performance improvement?&lt;/li&gt;
&lt;li&gt;Are database queries optimized for efficiency, with proper use of indexes and joins?&lt;/li&gt;
&lt;li&gt;Are N+1 query problems addressed by using eager loading where needed?&lt;/li&gt;
&lt;li&gt;Are computationally expensive tasks offloaded to background jobs, preventing slow responses or request timeouts for users?&lt;/li&gt;
&lt;li&gt;Does this code utilize memoization for methods that are called multiple times?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Which user roles are expected to gain access to the added resources?&lt;/li&gt;
&lt;li&gt;Are passwords securely stored using strong encryption, such as bcrypt?&lt;/li&gt;
&lt;li&gt;Are file uploads properly validated, restricted to safe file types, and securely stored?&lt;/li&gt;
&lt;li&gt;Does this code change expose any sensitive information, such as API keys, passwords, or usernames?&lt;/li&gt;
&lt;li&gt;Is sensitive data, like user data or credit card information, securely handled and stored?&lt;/li&gt;
&lt;li&gt;Are user inputs validated and sanitized to prevent SQL injection and XSS attacks?&lt;/li&gt;
&lt;li&gt;Are authorization and authentication correctly implemented? &lt;/li&gt;
&lt;li&gt;Are third-party libraries and gems regularly reviewed for known security vulnerabilities?&lt;/li&gt;
&lt;li&gt;Are API endpoints authenticated and authorized correctly?&lt;/li&gt;
&lt;li&gt;Are you sure that users cannot access API resources that do not belong to them?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Risk Management
&lt;/h3&gt;

&lt;p&gt;The main goal of this step is to mitigate the risk and do everything you can to reduce the chances of breaking the product.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preemptively draw attention to any areas of the code where you feel unsure.&lt;/li&gt;
&lt;li&gt;Determine the appropriate level of review needed based on the scope and impact of the code change.&lt;/li&gt;
&lt;li&gt;How risky is the PR? Is the feature actively used, or is it hidden and hasn't been realized yet? How critical is it?&lt;/li&gt;
&lt;li&gt;Is there any impact on other domains? Could the owner of the PR lack knowledge about how their work might impact other domains or code?&lt;/li&gt;
&lt;li&gt;How experienced is the PR author? Do you need to spend extra time re-checking the author's code, or can you trust it?&lt;/li&gt;
&lt;li&gt;Make a list of potential risks or issues that could arise from this change.&lt;/li&gt;
&lt;li&gt;It might be helpful to try running the code locally to ensure there are no issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Review any tests included with the code change to verify they adequately cover the functionality and edge cases.&lt;/li&gt;
&lt;li&gt;Is test coverage sufficient and correct? Are all border cases covered?&lt;/li&gt;
&lt;li&gt;Do the code changes have appropriate unit tests?&lt;/li&gt;
&lt;li&gt;Is the code designed to be easily testable?&lt;/li&gt;
&lt;li&gt;Have automated tests been added or relevant ones updated to cover the new functionality?&lt;/li&gt;
&lt;li&gt;Do the existing tests reasonably cover the code change, including unit, integration, and system tests?&lt;/li&gt;
&lt;li&gt;Are there additional test cases, inputs, or edge cases that should be considered?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logic Errors and Bugs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Can you envision any use cases in which the code does not behave as intended?&lt;/li&gt;
&lt;li&gt;Can you identify any inputs or external events that could potentially break the code?&lt;/li&gt;
&lt;li&gt;Ensure a failing test is written if the change is for a bug fix.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Soft Skills (How should Code Reviewers behave to address the problems they find?)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Collaboration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Be willing to collaborate with the author to resolve any issues or concerns that arise during the review process.&lt;/li&gt;
&lt;li&gt;Seek continuous improvement, not perfection.&lt;/li&gt;
&lt;li&gt;Consider using pair programming as an alternative or supplement to code reviews.&lt;/li&gt;
&lt;li&gt;Resolve conflicting opinions in a timely manner; don't let a PR sit around due to disagreement.&lt;/li&gt;
&lt;li&gt;Allocate time for code reviews - everyone really appreciates reviews. You get to know the people and the codebase too.&lt;/li&gt;
&lt;li&gt;Articulate your intention behind the feedback (suggestion vs request for a change vs improvement that can be done in a different ticket).&lt;/li&gt;
&lt;li&gt;Keep all the discussion online. If you contacted the reviewer by chat or email, bring relevant comments online.&lt;/li&gt;
&lt;li&gt;Be kind and empathetic.&lt;/li&gt;
&lt;li&gt;Reply within a reasonable timeframe.&lt;/li&gt;
&lt;li&gt;Avoid Gold Plating. Yes, we strive for code excellence but at some point, we have to be pragmatic (create a refactor ticket) and ship code.&lt;/li&gt;
&lt;li&gt;If you review only certain files that are part of a larger change or only certain aspects of the PR, such as the high-level design, privacy or security implications, etc., note in a comment what you reviewed.&lt;/li&gt;
&lt;li&gt;Discuss in person&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If there is a disagreement, have a quick in-person/video/IM chat to sort out what is going on - it‘s much easier to address all the little "Oh, I didn’t know"s in a single face-to-face, instead of back-and-forth via e-mail with long delays. “In person” doubly applies if you are disagreeing with another reviewer. And please make sure to record the outcomes on the review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Find an end&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you like things neat, it‘s tempting to go over a code review over and over until it’s perfect, dragging it out for longer than necessary. It‘s soul-deadening for the recipient, though. Keep in mind that “LGTM” does not mean “I vouch my immortal soul this will never fail”, but “looks good to me”. If it looks good, move on. (That doesn’t mean you shouldn‘t be thorough. It’s a judgment call.) And if there are bigger refactorings to be done, move them to a new PR.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always ask yourself if this decision really matters in the long run or if you're enforcing a subjective preference.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How to provide respectful and professional feedback?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use "we" instead of "you"&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"Why do you add this function?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Why do you think we should add this function?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use questions instead of commands or orders&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"We need tests for this."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"I don't see any unit tests for this function. Could we add some test cases to ensure it behaves as expected under various conditions?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Suggest, rather than instruct&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"Change the variable name to 'total_price'."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Consider changing the variable name to 'total_price'. It might make the code more descriptive."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Perhaps it would be better to rename the variable as 'total_price' for improved code clarity."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If you add something not very important, feel free to prefix it with something like 'Nit' to let the author know that it's just a point of polish that they could choose to ignore&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"The formatting here is inconsistent."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Nit: I noticed a minor inconsistency in the formatting. Could we align the indentation for consistency?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Optional: I noticed a minor inconsistency in the formatting. Could we align the indentation for consistency?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"FYI: I noticed a minor inconsistency in the formatting. Aligning the indentation for consistency is something to consider."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This makes the reviewer's intent explicit and helps authors prioritize the importance of different comments. It also helps prevent misunderstandings. For instance, without comment labels, authors might interpret all comments as mandatory, even if some are intended to be purely informational or optional.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Don’t be afraid to ask questions about how or why a thing works.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Could you provide some context on why you decided to use this specific algorithm instead of a more straightforward approach?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keep it light-hearted and use emojis&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"What do you think about this approach? 🤔 But I'm not sure if it works 😅"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Try to find positives and provide positive remarks&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Great work on this feature! 🚀"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Excellent job! ✅"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Well done 💪"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Thanks for fixing it so quickly 🎉"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Raise concerns in a polite and constructive manner&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"This is wrong"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"Maybe I'm missing something, but ..."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"I might be mistaken, but ...?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: &lt;em&gt;"I could be wrong, but ..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Be respectful and professional in your feedback, avoiding personal attacks, blame, or derogatory comments&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"This code is so sloppy and careless. Are you even trying?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"You're making the same mistake again."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"This code is terrible."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"You clearly don't understand how this works."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"You always forget to add comments."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"How could you not see this mistake? It's so obvious."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: &lt;em&gt;"Are you sure that software engineering is the right career path for you?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;To wrap it up, this article has introduced a clear code review process that can enhance code quality and speed up product development. We've talked about why code reviews matter and how they help teams. We've also pointed out who does what in code reviews and how it all works. These ideas are here to support organizations, code authors, and reviewers in making the most of code reviews and getting excellent outcomes.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>programming</category>
      <category>productivity</category>
      <category>review</category>
    </item>
    <item>
      <title>Understanding Transaction Isolation Levels in Rails with Simple Examples</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Sat, 29 Jul 2023 14:06:27 +0000</pubDate>
      <link>https://forem.com/vladhilko/understanding-transaction-isolation-levels-in-rails-with-simple-examples-388f</link>
      <guid>https://forem.com/vladhilko/understanding-transaction-isolation-levels-in-rails-with-simple-examples-388f</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we will discuss the usage of database transactions in a Rails application, with a particular focus on one of the &lt;strong&gt;ACID&lt;/strong&gt; principles - &lt;strong&gt;Isolation&lt;/strong&gt; - and its four levels. We will explore what these isolation levels are, why they are necessary, and the problems they aim to solve. Additionally, we will provide a straightforward working example of each isolation level using the Rails console. By the end of the article, you will have a decent understanding of isolation levels, and you will be able to verify their behavior on your own to ensure how they work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, a transaction is a mechanism that allows you to execute a group of operations in such a way that either they all execute (commit), or the system state will be as if they have not started to execute at all (rollback). In Rails, to use a transaction, you need to write your operations into the following block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;# ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To better understand transactions, we will explain what the &lt;strong&gt;ACID&lt;/strong&gt; principles are and the kind of problems they aim to solve. Afterward, we will delve deeply into one of these principles called Isolation, and we will explain why we need it and the problem-solving strategies that isolation suggests. Additionally, we will provide Rails examples to better grasp this concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACID
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ACID&lt;/strong&gt; is an acronym that stands for &lt;strong&gt;Atomicity&lt;/strong&gt;, &lt;strong&gt;Consistency&lt;/strong&gt;, &lt;strong&gt;Isolation&lt;/strong&gt;, and &lt;strong&gt;Durability&lt;/strong&gt;. These four principles highlight the potential problems that may occur and what we should be aware of. For two of them (&lt;strong&gt;Consistency&lt;/strong&gt; and &lt;strong&gt;Durability&lt;/strong&gt;), we don't have any control, and we should rely on the database implementation and trust it. For the other two (&lt;strong&gt;Atomicity&lt;/strong&gt; and &lt;strong&gt;Isolation&lt;/strong&gt;), we can have more control, and we will be focusing on them more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomicity
&lt;/h3&gt;

&lt;p&gt;The first principle is &lt;strong&gt;Atomicity&lt;/strong&gt;. To remember this, we can think about the atom. You can't split the atom. The same goes for an atomicity transaction; you can't just split it. It should be all or nothing. Let's provide an example of breaking this principle:&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 c&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: nil, status: nil&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_transaction_atomicity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;animal&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;name: &lt;/span&gt;&lt;span class="s1"&gt;'Cat'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Error'&lt;/span&gt;
  &lt;span class="n"&gt;animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;check_transaction_atomicity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# RuntimeError: Error&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;
&lt;span class="c1"&gt;# #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: nil&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, our data is now saved in an inconsistent state. The name was saved, but the status wasn't. This is why we violate the atomicity principle because we must commit all or nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency
&lt;/h3&gt;

&lt;p&gt;The second principle is &lt;strong&gt;Consistency&lt;/strong&gt;. Consistency means that the database must always move from one valid state to another, ensuring that data meets all defined rules and constraints after a transaction is completed. Consistency ensures that any illegal states resulting from the transaction are automatically rolled back to the previous valid state.&lt;/p&gt;

&lt;p&gt;If Consistency doesn't exist during the database transaction, then the following code would successfully work even if a strict &lt;code&gt;NOT NULL&lt;/code&gt; constraint was present on the id column.&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_transaction_consistency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;animal&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;id: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;check_transaction_consistency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; ActiveRecord::NotNullViolation: Mysql2::Error: Column 'id' cannot be null&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can't control this principle; that's why we should fully rely on the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolation
&lt;/h3&gt;

&lt;p&gt;The isolation principle doesn't allow interference with any data inside the transaction until the transaction is released. There are three potential problems that may occur and break isolation, all of them are connected with reading data during transaction execution, here they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dirty Read&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phantom Read&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To remember these problems, you can think about the CRUD operations (create, read, update, and delete):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dirty Read&lt;/strong&gt; may appear when we &lt;code&gt;READ&lt;/code&gt; data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Repeatable Read&lt;/strong&gt; may appear when we &lt;code&gt;UPDATE&lt;/code&gt; data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom Read&lt;/strong&gt;. may appear when we &lt;code&gt;CREATE&lt;/code&gt; data &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's discuss some abstract examples to gain a rough understanding of the problems. In the next chapter, we will provide a real example to illustrate these concepts more concretely.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dirty Read
&lt;/h4&gt;

&lt;p&gt;When a transaction reads data from another uncommitted transaction, we call this violation a &lt;strong&gt;Dirty Read&lt;/strong&gt;. This problem has such a name because it allows us to read 'Dirty' data that is unfinished and may not be accurate. An abstract example to demonstrate this violation would look 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="c1"&gt;# rails c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: nil&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dirty_read_transaction&lt;/span&gt;
  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'dirty_read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Unexpected Error'&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;try_to_read_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Animal&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;status&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;dirty_read_transaction&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_read_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; 'dirty_read'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the problem here is that we have access to the updated data even if the data is not fully committed and may be rolled back later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Non-Repeatable Read
&lt;/h4&gt;

&lt;p&gt;A non-repeatable read occurs when, during the course of a transaction, a row is retrieved twice, and the values within the row differ between reads. This problem has such a name because the same data inside the transaction may not be equal ("not repeated") by the end of the transaction execution. To demonstrate it, we can use the following snippet:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: 'repeatable_read'&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;non_repeatable_read_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'non_repeatable_read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;non_repeatable_read_transaction&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: repeatable_read'&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: non_repeatable_read'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you may notice, the value between the two reads is different ("not repeated").&lt;/p&gt;

&lt;h4&gt;
  
  
  Phantom Read
&lt;/h4&gt;

&lt;p&gt;A phantom read occurs when, in the course of a transaction, new rows are added by another transaction to the records being read. It has such a name because the new record appears in the middle of transaction execution out of nowhere, like a 'phantom', because there were no such records when we just started the transaction execution.&lt;/p&gt;

&lt;p&gt;Let's provide an example of breaking this principle:&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;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# [&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca748 id: 1, name: "Cat", status: nil&amp;gt;,&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca680 id: 2, name: "Dog", status: nil&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phantom_read_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"We have the following animals &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"We have the following animals &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Wolf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;phantom_read_transaction&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; We have the following animals [1, 2]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; We have the following animals [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durability
&lt;/h3&gt;

&lt;p&gt;The final aspect of the ACID approach to database management is durability.&lt;/p&gt;

&lt;p&gt;Durability ensures that changes made to the database (transactions) that are successfully committed will survive permanently, even in the case of system failures. This ensures that the data within the database will not be corrupted by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service outages&lt;/li&gt;
&lt;li&gt;Crashes&lt;/li&gt;
&lt;li&gt;Other cases of failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a look at the following 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 c&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: nil, status: nil&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_transaction_durability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;animal&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;name: &lt;/span&gt;&lt;span class="s1"&gt;'Cat'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;check_transaction_durability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# power outage&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Cat'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we didn't apply the Durability principle here, then after some unexpected crashes, we would lose the data that was already committed. We can't control this; that's why we should fully rely on the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transaction Isolation Levels
&lt;/h2&gt;

&lt;p&gt;To solve the problems that were mentioned above in the &lt;strong&gt;ACID&lt;/strong&gt; isolation chapter, the Database provides 4 isolation levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read Uncommitted&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read Committed&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeatable Read&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serializable&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these levels is stronger than the previous one and solves all previous problems by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read Uncommitted
&lt;/h3&gt;

&lt;p&gt;At this level, we can just rollback the transaction; everyone can update and read data inside during execution. At this level, there's no isolation at all. And as the name suggests, this isolation level is allowed to read the data from the transaction that hasn't been committed. Let's take a look at the 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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: nil&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_uncommitted_transaction&lt;/span&gt;
  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'read_uncommitted'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Unexpected Error'&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;try_to_read_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"We have access to the uncommitted value inside the transaction, and the status value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&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="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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_uncommitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;read_uncommitted_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_uncommitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_read_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; We have access to the uncommitted value inside the transaction, and the status value is: read_uncommitted&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Error (RuntimeError)&lt;/span&gt;

&lt;span class="no"&gt;Animal&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;status&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'nil'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we had access to the uncommitted status inside the transaction, but after rollback, the final status value is still nil.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read Committed
&lt;/h3&gt;

&lt;p&gt;The second isolation level allows us to solve the &lt;strong&gt;Dirty Read&lt;/strong&gt; problem but doesn't solve the other two (&lt;strong&gt;Non-Repeatable Read&lt;/strong&gt; and &lt;strong&gt;Phantom Read&lt;/strong&gt;). As the name of this isolation level suggests, now we can't read uncommitted data, only the committed one. First of all, let's try to run the same example but we'll change the isolation level from &lt;code&gt;:read_uncommitted&lt;/code&gt; to &lt;code&gt;:read_committed&lt;/code&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;# rails c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: nil&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_committed_transaction&lt;/span&gt;
  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'read_committed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'Unexpected Error'&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;try_to_read_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"We don't have access to uncommitted value inside the transaction, and the status value is still: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&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="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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_committed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;read_committed_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_committed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_read_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; We don't have access to uncommitted value inside the transaction, and the status value is still:&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Error (RuntimeError)&lt;/span&gt;

&lt;span class="no"&gt;Animal&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;status&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'nil'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see here, we don't have access to the value that has been updated inside the other transaction because the second transaction can't read the data from the first one. Do we have any other problems with this isolation level? Yes, as I said, this transaction can solve only the &lt;strong&gt;Dirty Read&lt;/strong&gt;  problem, and &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt; Read and &lt;strong&gt;Phantom Read&lt;/strong&gt; still exist. Let's take a look:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: 'initial'&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_committed_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'non_repeatable_read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_committed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;read_committed_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :read_committed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: initial'&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: non_repeatable_read'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the second transaction is changing the value inside the first one, and we receive non-repeatable values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repeatable Read
&lt;/h3&gt;

&lt;p&gt;The third isolation level allows us to solve the  &lt;strong&gt;Dirty Read&lt;/strong&gt; and  &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt; problems, but still can't handle the &lt;strong&gt;Phantom Read&lt;/strong&gt;. Let's run the code from the previous example to make sure that &lt;strong&gt;Non Repeatable Read&lt;/strong&gt; won't be present here:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x00000001112ad408 id: 1, name: "Cat", status: 'initial'&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;repeatable_read_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"Current value is: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&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;status&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'non_repeatable_read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :repeatable_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;repeatable_read_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :repeatable_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_update_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: initial'&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Current value is: initial'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the previous problem disappeared here, and we have "repeatable" values.&lt;/p&gt;

&lt;p&gt;Now, let's take a look if we really have the  &lt;strong&gt;Phantom Read&lt;/strong&gt; problem here:&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 c &lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# [&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca748 id: 1, name: "Cat", status: nil&amp;gt;,&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca680 id: 2, name: "Dog", status: nil&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;repeatable_read_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Inside the current transaction, we have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"After the time gap, we still have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'phantom_read_triggered'&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;"After we triggered the update all operation, we have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Wolf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :repeatable_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;repeatable_read_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :repeatable_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; Inside the current transaction, we have the following animals [1, 2]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; After the time gap, we still have the following animals [1, 2]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; After we triggered the update all operation, we have the following animals [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, a new ID has been added, and it was not expected. Let's try to fix it using the latest isolation level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serializable
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Serializable&lt;/strong&gt; isolation level is the strongest among all the isolation levels. It provides the highest level of isolation and ensures that the transactions are executed in a way that is equivalent to running them sequentially, one after the other. This means that no concurrent execution of transactions can result in anomalies like &lt;strong&gt;Dirty Read&lt;/strong&gt;, &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;, or &lt;strong&gt;Phantom Read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's use the &lt;strong&gt;Serializable&lt;/strong&gt; isolation level to fix the &lt;strong&gt;Phantom Read&lt;/strong&gt; problem in the previous 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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# [&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca748 id: 1, name: "Cat", status: nil&amp;gt;,&lt;/span&gt;
&lt;span class="c1"&gt;#  #&amp;lt;Animal:0x000000010b7ca680 id: 2, name: "Dog", status: nil&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serializable_transaction&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Inside the current transaction, we have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&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;"After the time gap, we still have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="no"&gt;Animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'phantom_read_fixed'&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;"After we triggered the update all operation, we have the following animals: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&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;def&lt;/span&gt; &lt;span class="nf"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Wolf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :serializable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;serializable_transaction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&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="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: :serializable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;try_to_create_data_inside_transaction_during_execution&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; Inside the current transaction, we have the following animals [1, 2]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; After the time gap, we still have the following animals [1, 2]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; After we triggered the update all operation, we have the following animals [1, 2]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see there's no phantom records and all data were properly isolated inside the transaction.&lt;/p&gt;

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

&lt;p&gt;Transaction isolation levels are essential in managing data consistency and concurrency in databases. We discussed four isolation levels: &lt;strong&gt;Read Uncommitted&lt;/strong&gt;, &lt;strong&gt;Read Committed&lt;/strong&gt;, &lt;strong&gt;Repeatable Read&lt;/strong&gt;, and &lt;strong&gt;Serializable&lt;/strong&gt;. Each level offers different degrees of isolation and addresses specific problems related to concurrent transactions. We demonstrated how each isolation level affects transactions through practical examples. By carefully choosing the appropriate isolation level, developers can strike a balance between data integrity and performance, ensuring that transactions behave predictably and maintain data consistency even in a multi-user environment.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>database</category>
      <category>transaction</category>
    </item>
    <item>
      <title>How to Add Database Triggers in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Wed, 26 Jul 2023 14:45:25 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-add-database-triggers-in-ruby-on-rails-2b8i</link>
      <guid>https://forem.com/vladhilko/how-to-add-database-triggers-in-ruby-on-rails-2b8i</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to discuss the usage of database triggers in Ruby on Rails applications. We will cover what they are, why they are essential, the problems they solve, their pros and cons, when they are allowed to be used, and when they are not. Additionally, we will provide three simple examples of how to add these triggers in a Rails application. By the end of this article, you will have a better understanding and a broader perspective on using DB triggers in Rails applications.&lt;/p&gt;

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

&lt;p&gt;In simple terms, a database trigger is a function that is automatically invoked when an INSERT, UPDATE, or DELETE operation is performed on a table. You can think of it as an ActiveRecord callback that is executed at the database level. Sometimes, they are even referred to as SQL callbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use cases
&lt;/h3&gt;

&lt;p&gt;We usually use triggers to solve the following problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auditing and Logging&lt;/li&gt;
&lt;li&gt;Database schema refactoring (denormalization, renaming columns, splitting a column into two columns, etc.)&lt;/li&gt;
&lt;li&gt;Populating Summary Tables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pros/Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Accuracy and Consistency&lt;/li&gt;
&lt;li&gt;Reduced Complexity in Application Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you use triggers, ActiveRecord has no way of knowing when your records have changed. This means ActiveRecord objects will retain outdated data until they are reloaded.&lt;/li&gt;
&lt;li&gt;Don't overdo it. Delegating too much business logic to triggers can create problems in the future (Debugging Difficulties, Testing Challenges, Performance Impact, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While triggers can be useful for certain functionalities, it's essential to use them judiciously and consider alternative approaches for implementing business logic within the application code when possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Adding a Trigger Inside Rails Console
&lt;/h2&gt;

&lt;p&gt;In this first example, we will create a DB trigger directly in the Rails console and observe how it works. Let's consider two database tables, &lt;code&gt;animals&lt;/code&gt; and &lt;code&gt;removed_animals&lt;/code&gt;, each containing only two columns - &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;. Our goal is to add a trigger that duplicates an animal record into the &lt;code&gt;removed_animals&lt;/code&gt; table every time we delete a record from the main &lt;code&gt;animals&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Let's see the current state:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently, the &lt;code&gt;removed_animals&lt;/code&gt; table is empty and doesn't contain any removed records.&lt;/p&gt;

&lt;p&gt;Now, let's execute a manual insertion into &lt;code&gt;removed_animals&lt;/code&gt; to see what we expect to run in the trigger:&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 c&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"INSERT INTO `removed_animals` (`id`, `name`) VALUES (1, 'animal_name')"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt;  [#&amp;lt;RemovedAnimal:0x000000010bc4be98 id: 1, name: "animal_name"&amp;gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As seen above, we manually inserted a record into &lt;code&gt;removed_animals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's implement the trigger and check how it works:&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 c&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="s2"&gt;"
CREATE TRIGGER save_removed_animal_trigger
AFTER DELETE ON animals
FOR EACH ROW
INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`)
"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the trigger in place, let's test it further:&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;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Another Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;Animal:0x000000010dc41f18 id: 10, name: "Animal"&amp;gt;, #&amp;lt;Animal:0x000000010dc41e50 id: 11, name: "Another Animal"&amp;gt;]&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;RemovedAnimal:0x000000010de6b550 id: 10, name: "Animal"&amp;gt;, #&amp;lt;RemovedAnimal:0x000000010de6b488 id: 11, name: "Another Animal"&amp;gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, all our destroyed animal records have been saved to the &lt;code&gt;removed_animals&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Now, let's drop this trigger and recreate the same logic using the correct Rails approach in Example 2:&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 c&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DROP TRIGGER save_removed_animal_trigger"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2: Adding Triggers Using the hair_trigger Gem
&lt;/h2&gt;

&lt;p&gt;In this second example, we will achieve the same functionality using a clearer approach by utilizing the &lt;a href="https://github.com/jenseng/hair_trigger"&gt;hair_trigger&lt;/a&gt; gem. Let's start by installing it:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'hairtrigger'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After installing the gem, we can declare triggers in our models and use a rake task to auto-generate the appropriate migration:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="s2"&gt;"INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`)"&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;To generate the migration, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake db:generate_trigger_migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This task will generate the migration file for us:&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;# db/migrate/20230725122552_create_trigger_animals_delete.rb&lt;/span&gt;

&lt;span class="c1"&gt;# This migration was auto-generated via `rake db:generate_trigger_migration'.&lt;/span&gt;
&lt;span class="c1"&gt;# While you can edit this file, any changes you make to the definitions here&lt;/span&gt;
&lt;span class="c1"&gt;# will be undone by the next auto-generated trigger migration.&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateTriggerAnimalsDelete&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="n"&gt;create_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_after_delete_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:compatibility&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="s2"&gt;"INSERT INTO `removed_animals` (`id`, `name`) VALUES (OLD.`id`, OLD.`name`);"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;drop_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_after_delete_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, execute the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And test if the trigger works as expected, similar to what we did in Example 1:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Another Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;Animal:0x000000010bbb1730 id: 12, name: "Animal"&amp;gt;, #&amp;lt;Animal:0x000000010bbb15c8 id: 13, name: "Another Animal"&amp;gt;]&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; []&lt;/span&gt;

&lt;span class="no"&gt;RemovedAnimal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;RemovedAnimal:0x000000010bd81498 id: 12, name: "Animal"&amp;gt;, #&amp;lt;RemovedAnimal:0x000000010bd813d0 id: 13, name: "Another Animal"&amp;gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, everything works as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: Adding a Trigger to Support Column Renaming
&lt;/h2&gt;

&lt;p&gt;In this third example, we will add a trigger that allows us to rename a column with zero downtime. To achieve this, we'll follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a new column.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a trigger to dual-write to both columns.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Backfill the new column with a copy of the old column’s values.&lt;/li&gt;
&lt;li&gt;Start using the new column throughout the whole application.&lt;/li&gt;
&lt;li&gt;Drop the old column.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For now, we're focused on step 2 - creating a new trigger to write to both columns simultaneously. Let's examine what we want to achieve. Suppose we have an &lt;code&gt;animals&lt;/code&gt; table with two columns -&amp;gt; &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;, and we decide to rename the &lt;code&gt;name&lt;/code&gt; column to &lt;code&gt;full_name&lt;/code&gt;, for example. So, we need to add this new column and observe the current behavior:&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 c&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x000000010e823980 id: 14, name: "Animal", full_name: nil&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;name: &lt;/span&gt;&lt;span class="s1"&gt;'Updated Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x000000010e9f9d90 id: 14, name: "Updated Animal", full_name: nil&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As seen above, when we create or update an animal's name, the &lt;code&gt;full_name&lt;/code&gt; column remains blank. However, if we replace the &lt;code&gt;name&lt;/code&gt; column with &lt;code&gt;full_name&lt;/code&gt;, all values should synchronize. This is where a trigger can be immensely helpful:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:insert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="s2"&gt;"SET NEW.full_name = NEW.name;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;of&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="k"&gt;do&lt;/span&gt;
    &lt;span class="s2"&gt;"SET NEW.full_name = NEW.name;"&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;After adding the triggers to the model, we need to run the following command to generate the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake db:generate_trigger_migration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rake task generates the migration file for us:&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;# db/migrate/20230725154353_create_triggers_animals_insert_or_animals_update.rb&lt;/span&gt;

&lt;span class="c1"&gt;# This migration was auto-generated via `rake db:generate_trigger_migration'.&lt;/span&gt;
&lt;span class="c1"&gt;# While you can edit this file, any changes you make to the definitions here&lt;/span&gt;
&lt;span class="c1"&gt;# will be undone by the next auto-generated trigger migration.&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateTriggersAnimalsInsertOrAnimalsUpdate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="n"&gt;create_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_before_insert_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:compatibility&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:insert&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="s2"&gt;"SET NEW.full_name = NEW.name;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_before_update_of_name_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:compatibility&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:update&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
        &lt;span class="nf"&gt;of&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="k"&gt;do&lt;/span&gt;
      &lt;span class="s2"&gt;"SET NEW.full_name = NEW.name;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;drop_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_before_insert_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;drop_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"animals_before_update_of_name_row_tr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"animals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:generated&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Let's run this migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check if it works:&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 c&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'Animal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x000000010e5723f8 id: 21, name: "Animal", full_name: nil&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x000000010e5723f8 id: 21, name: "Animal", full_name: "Animal"&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;animal&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;name: &lt;/span&gt;&lt;span class="s1"&gt;'New Name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;Animal:0x000000010e5723f8 id: 21, name: "New Name", full_name: "Animal"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, everything works as expected, and all name values are duplicated into the &lt;code&gt;full_name&lt;/code&gt; column.&lt;/p&gt;

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

&lt;p&gt;In this article, we explored three examples of adding triggers to a Rails application. From creating simple triggers in the Rails console to using the &lt;code&gt;hair_trigger&lt;/code&gt; gem for a clearer approach. Triggers offer data accuracy and automation but should be used judiciously to avoid potential challenges.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>database</category>
      <category>trigger</category>
    </item>
    <item>
      <title>Data Migration Strategies in Ruby on Rails: The Right Way to Manage Missing Data</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Sat, 22 Jul 2023 16:13:09 +0000</pubDate>
      <link>https://forem.com/vladhilko/data-migration-strategies-in-ruby-on-rails-the-right-way-to-manage-missing-data-3dbe</link>
      <guid>https://forem.com/vladhilko/data-migration-strategies-in-ruby-on-rails-the-right-way-to-manage-missing-data-3dbe</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we're going to discuss the possible strategies for migrating, generating, and backfilling data in a Rails application. We'll implement them, improve them, consider their pros and cons, and discuss which ones are better to use in different scenarios. By the end of the article, we will have a full picture of the different ways to solve data migration problems.&lt;/p&gt;

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

&lt;p&gt;In simple terms, data migration is the process of adding, updating, or transferring some data inside your application. The most popular cases for data migration are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Backfilling column data&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Moving column data from one table to another&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Generating new database records&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Updating corrupted or invalid data with correct values&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Removing unused data&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll consider 3 different ways to do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Direct Data Manipulation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rake Task&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Migration Gem&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Direct Data Manipulation
&lt;/h2&gt;

&lt;p&gt;The first option is the simplest one: we'll just add missing data via &lt;code&gt;rails c&lt;/code&gt; or through a direct database connection in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy&lt;/li&gt;
&lt;li&gt;No need to implement anything new&lt;/li&gt;
&lt;li&gt;It's fast because data migration can be done in minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too risky; changes may not end up as intended&lt;/li&gt;
&lt;li&gt;Possible access and security problems&lt;/li&gt;
&lt;li&gt;There are no tests and code reviews, so we can't be sure of the quality&lt;/li&gt;
&lt;li&gt;Lack of control; you don't know who ran the migration or why they ran it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rake Task
&lt;/h2&gt;

&lt;p&gt;The second option is the rake task. In this chapter, we will try to understand how to properly add rake tasks, ensure that they work correctly, learn their pros and cons, and explore how they can be used for data migration. We will start by adding the simplest rake task, and then we will proceed to improve its structure, cover the logic with tests, and consider using best practices for writing data migration using rake tasks.&lt;/p&gt;

&lt;p&gt;Let's imagine that we have an Animal model with the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kind&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we need to change the status value from &lt;code&gt;nil&lt;/code&gt; to &lt;code&gt;reserved&lt;/code&gt; for all animals that we created before today. How can we do it? Let's start by adding a simple rake task template.&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&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;'Updating animal status...'&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;And check that it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:backfill_statuses
&lt;span class="c"&gt;# =&amp;gt; Updating animal status...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The task has been executed and works as expected. Now, let's add the actual code with the database update. It will look like the following:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's check if it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:backfill_statuses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rake task has been executed, and the database values are updated accordingly. That's it. Our main scenario works as expected, but there's still some room for improvements. Let's take a look at what we can do to make our task more reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improvements
&lt;/h3&gt;

&lt;p&gt;There are 5 areas that we can potentially improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Display results in the console for visibility&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ensure data consistency with transactions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize DB requests&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolate the rake task code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Display results in the console for visibility
&lt;/h3&gt;

&lt;p&gt;As you may have noticed, the rake task above hasn't shown any output. It can be a real problem because you don't know whether it was run successfully or not, and you will spend much time trying to check it by yourself on production data. Let's fix this problem:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Before running the rake task, there were &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; animals in the 'reserved' state."&lt;/span&gt;

    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&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;"After running the rake task, there are now &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; animals in the 'reserved' state."&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 run the rake task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:backfill_statuses

&lt;span class="c"&gt;# =&amp;gt; Before running the rake task, there were 0 animals in the 'reserved' state.&lt;/span&gt;
&lt;span class="c"&gt;# =&amp;gt; After running the rake task, there are now 101 animals in the 'reserved' state.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the updated code, we display the number of animals in the 'reserved' state both before and after running the rake task, providing better visibility and ensuring that the task is executed successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ensure data consistency with transactions
&lt;/h3&gt;

&lt;p&gt;What happens if some unexpected errors appear in the middle of data migration? Right now, we don't handle it. Even if it's not critical for the example that we've provided, in general, we should not forget to wrap such kind of data manipulation into a transaction to keep a consistent data state.&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&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="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&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;By adding the &lt;code&gt;ActiveRecord::Base.transaction&lt;/code&gt; block, we ensure that all the updates are executed as a single atomic operation. If any error occurs during the data migration, the transaction will be rolled back, and the data will remain unchanged, maintaining data consistency and integrity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S.&lt;/em&gt; If you have very large tables, you need to keep in mind that simply UPDATE all rows would cause a lock against writes on the whole table. That is why updates must be done in batches. The migration must not run within a transaction for the same reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimize DB requests
&lt;/h3&gt;

&lt;p&gt;We have already handled this problem in our rake task, but it's important to mention that we should use the optimal database solution if possible. For example, someone could write our task like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;animal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&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;The following code will trigger an SQL update request for every animal from the list, making it non-optimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.346040 &lt;span class="c"&gt;#67787] DEBUG -- :   Animal Load (1.6ms)  SELECT `animals`.* FROM `animals` WHERE `animals`.`status` IS NULL AND (created_at &amp;lt; '2023-07-21')&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.346735 &lt;span class="c"&gt;#67787] DEBUG -- :   ↳ lib/tasks/animals/backfill_statuses.rake:10:in `block (2 levels) in &amp;lt;main&amp;gt;'&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.371908 &lt;span class="c"&gt;#67787] DEBUG -- :   TRANSACTION (2.2ms)  BEGIN&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.372737 &lt;span class="c"&gt;#67787] DEBUG -- :   ↳ lib/tasks/animals/backfill_statuses.rake:11:in `block (3 levels) in &amp;lt;main&amp;gt;'&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.375091 &lt;span class="c"&gt;#67787] DEBUG -- :   Animal Update (2.2ms)  UPDATE `animals` SET `animals`.`status` = 'reserved', `animals`.`updated_at` = '2023-07-21 07:50:58.368697' WHERE `animals`.`id` = 1&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.375713 &lt;span class="c"&gt;#67787] DEBUG -- :   ↳ lib/tasks/animals/backfill_statuses.rake:11:in `block (3 levels) in &amp;lt;main&amp;gt;'&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.381169 &lt;span class="c"&gt;#67787] DEBUG -- :   TRANSACTION (5.0ms)  COMMIT&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.381524 &lt;span class="c"&gt;#67787] DEBUG -- :   ↳ lib/tasks/animals/backfill_statuses.rake:11:in `block (3 levels) in &amp;lt;main&amp;gt;'&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.383624 &lt;span class="c"&gt;#67787] DEBUG -- :   TRANSACTION (1.3ms)  BEGIN&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.384250 &lt;span class="c"&gt;#67787] DEBUG -- :   ↳ lib/tasks/animals/backfill_statuses.rake:11:in `block (3 levels) in &amp;lt;main&amp;gt;'&lt;/span&gt;
D, &lt;span class="o"&gt;[&lt;/span&gt;2023-07-21T09:50:58.385792 &lt;span class="c"&gt;#67787] DEBUG -- :   Animal Update (1.4ms)  UPDATE `animals` SET `animals`.`status` = 'reserved', `animals`.`updated_at` = '2023-07-21 07:50:58.381901' WHERE `animals`.`id` = 2&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's why it always makes sense to try to find a way to do it in a single DB operation, as we did with the following code:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will trigger only one DB request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Animal Update All &lt;span class="o"&gt;(&lt;/span&gt;6.8ms&lt;span class="o"&gt;)&lt;/span&gt;  UPDATE &lt;span class="sb"&gt;`&lt;/span&gt;animals&lt;span class="sb"&gt;`&lt;/span&gt; SET &lt;span class="sb"&gt;`&lt;/span&gt;animals&lt;span class="sb"&gt;`&lt;/span&gt;.&lt;span class="sb"&gt;`&lt;/span&gt;status&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'reserved'&lt;/span&gt; WHERE &lt;span class="sb"&gt;`&lt;/span&gt;animals&lt;span class="sb"&gt;`&lt;/span&gt;.&lt;span class="sb"&gt;`&lt;/span&gt;status&lt;span class="sb"&gt;`&lt;/span&gt; IS NULL AND &lt;span class="o"&gt;(&lt;/span&gt;created_at &amp;lt; &lt;span class="s1"&gt;'2023-07-21'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's no way to update something in a single DB request, at least you should consider using &lt;a href="https://apidock.com/rails/ActiveRecord/Batches/find_each"&gt;batches&lt;/a&gt; as a good practice:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find_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;animal&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;animal&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&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;P.S. To see SQL logs from the rake task, you can add the following code inside:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&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="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STDOUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Isolate the rake task code
&lt;/h3&gt;

&lt;p&gt;One not so obvious problem that can occur when you have many rake tasks is a lack of encapsulation. Let's take a look at the following two rake tasks and try to guess what can be wrong here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rake animals:task1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task1.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task1: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&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;def&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;
    &lt;span class="s1"&gt;'Hello world from Task 1!'&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;ul&gt;
&lt;li&gt;
&lt;code&gt;rake animals:task2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task2.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task2: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;puts&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;def&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;
    &lt;span class="s1"&gt;'Hello world from Task 2!'&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 run both of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:task1
&lt;span class="c"&gt;# =&amp;gt; Hello world from Task 2!&lt;/span&gt;
rake animals:task2
&lt;span class="c"&gt;# =&amp;gt; Hello world from Task 2!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Have you noticed? That's not what we expected! The second rake task overrode the method value from the first one! And that's quite dangerous and unexpected if you were to use something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rake animals:task1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task1.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task1: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&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;query&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&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;ul&gt;
&lt;li&gt;
&lt;code&gt;lib/tasks/animals/task2.rake&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task2.rake&lt;/span&gt;

&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task2: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&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;query&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;And run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:task1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You would remove all your records instead of the desired subset!&lt;/p&gt;

&lt;p&gt;How can we fix it?&lt;/p&gt;

&lt;p&gt;We need to wrap our rake tasks into the &lt;code&gt;Rake::DSL&lt;/code&gt; class like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rake animals:task1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task1.rake&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Tasks&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Animals&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Task1&lt;/span&gt;

      &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
        &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task1: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="nb"&gt;puts&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="kp"&gt;private&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;
        &lt;span class="s1"&gt;'Hello world from Task 1!'&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;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Animals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rake animals:task2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# lib/tasks/animals/task2.rake&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Tasks&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Animals&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Task2&lt;/span&gt;

      &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
        &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;task2: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="nb"&gt;puts&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="kp"&gt;private&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;message&lt;/span&gt;
        &lt;span class="s1"&gt;'Hello world from Task 2!'&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;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Animals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rake animals:task1
&lt;span class="c"&gt;# =&amp;gt; Hello world from Task 1!&lt;/span&gt;
rake animals:task2
&lt;span class="c"&gt;# =&amp;gt; Hello world from Task 2!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now everything works as expected. Let's apply the same isolation for our &lt;code&gt;backfill_statuses&lt;/code&gt; rake task.&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="c1"&gt;# lib/tasks/animals/backfill_statuses.rake&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Tasks&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Animals&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BackfillStatuses&lt;/span&gt;

      &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
        &lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:animals&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="s2"&gt;"Update animal status to 'reserved' for animals created before today"&lt;/span&gt;
          &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;backfill_statuses: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Tasks&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Animals&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BackfillStatuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add tests
&lt;/h3&gt;

&lt;p&gt;The last thing that we're going to do to ensure quality is to add tests. Let's see how we can test the rake tasks.&lt;/p&gt;

&lt;p&gt;First of all, we need to define some code to load our tasks:&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;# spec/support/tasks.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&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="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_tasks&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And include it in the &lt;code&gt;spec/rails_helper.rb&lt;/code&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;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'support/tasks'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let's add our test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/tasks/animals/backfill_statuses_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'rake animals:backfill_statuses'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :task&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animals:backfill_statuses'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:expected_output&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;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;TEXT&lt;/span&gt;&lt;span class="sh"&gt;
      Before running the rake task, there were 1 animals in the 'reserved' state.
      After running the rake task, there are now 2 animals in the 'reserved' state.
&lt;/span&gt;&lt;span class="no"&gt;    TEXT&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal_1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;created_at: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;created_at: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal_3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;created_at: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'another_status'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal_4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;created_at: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;animal_1&lt;/span&gt;
    &lt;span class="n"&gt;animal_2&lt;/span&gt;
    &lt;span class="n"&gt;animal_3&lt;/span&gt;
    &lt;span class="n"&gt;animal_4&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"update animal status to 'reserved' for animals created before today"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;animal_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_stdout&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reserved'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'another_status'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_nil&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;That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Migration Gem
&lt;/h2&gt;

&lt;p&gt;The third option is to use the &lt;a href="https://github.com/ilyakatz/data-migrate"&gt;data-migrate&lt;/a&gt; gem.&lt;/p&gt;

&lt;p&gt;Let's add this gem to our project:&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'data_migrate'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And execute:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Now you can generate a data migration as you would generate a schema migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g data_migration backfill_animal_statuses

# =&amp;gt; db/data/20230721111716_backfill_animal_statuses.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add some code to the generated file to check if it actually works:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BackfillAnimalStatuses&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Test Data Migration'&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;down&lt;/span&gt;
    &lt;span class="c1"&gt;# do nothing&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;To run the migration, we need to use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake data:migrate
# or
rake db:migrate:with_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== 20230721111716 BackfillAnimalStatuses: migrating ===========================
Test Data Migration
== 20230721111716 BackfillAnimalStatuses: migrated (0.0000s) ==================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This migration can be run only once. So let's remove it and generate another one and add real code inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g data_migration backfill_animal_statuses

# =&amp;gt; db/data/20230721112534_backfill_animal_statuses.rbb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what we get after adding our business logic code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/data/20230721112534_backfill_animal_statuses.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BackfillAnimalStatuses&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at &amp;lt; ?'&lt;/span&gt;&lt;span class="p"&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;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&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;status: &lt;/span&gt;&lt;span class="s1"&gt;'reserved'&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;down&lt;/span&gt;
    &lt;span class="c1"&gt;# do nothing&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;And let's run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake data:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== Data =======================================================================
== 20230721112534 BackfillAnimalStatuses: migrating ===========================
== 20230721112534 BackfillAnimalStatuses: migrated (0.0221s) ==================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, data migration works by the same logic as scheme migration, but instead of saving the last running migration version into the &lt;code&gt;schema_migrations&lt;/code&gt; table, data migration saves the version into another table called &lt;code&gt;data_migrations&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's important to mention that data migration should be irreversible in most cases, but we don't want to raise an explicit error as it would prevent rollback for the scheme structure changes. Instead, we just leave the &lt;code&gt;down&lt;/code&gt; method empty. For this reason, it would be better to design the migration in an idempotent way, to be able to run it several times if possible.&lt;/p&gt;

&lt;p&gt;Data Migration doesn't provide any additional advantages except for what we discussed above. Therefore, we still need to think about the problems that we solved for the rake task, such as displaying output, adding transactions, optimizing DB requests, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison of Rake Task and Data Migration Gem
&lt;/h2&gt;

&lt;p&gt;Let's compare these two solutions and decide which one we should use and under what conditions:&lt;/p&gt;

&lt;h3&gt;
  
  
  When does a rake task fit better?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When you want to have the ability to select the exact time and day when you want to run it.&lt;/li&gt;
&lt;li&gt;When you want to have the ability to select the platform where you want to run it (for example, staging or production).&lt;/li&gt;
&lt;li&gt;When you want to run the same rake task several times.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When does the data migration gem fit better?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When you want to make sure that data will be added automatically and no one forgets to run something.&lt;/li&gt;
&lt;li&gt;When schema migration order is important for you (for example, schema migration adds a new column and data migration backfills this column).&lt;/li&gt;
&lt;li&gt;When you want to run it on all environments without additional effort.&lt;/li&gt;
&lt;li&gt;When you need to run the migration only once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in general, the rake task is much more flexible and testable and can cover the same tasks as the data migration but may require more effort. Data migrations are much more strict but provide some automations and strict execution order that are connected with schema changes.&lt;/p&gt;

&lt;p&gt;For example, the data migration gem suits very well if you need to support some database schema restructurization and you don't want to miss the data during these changes. For instance, if you need to rename a column and you want to copy the values from the old one to a new one, then set &lt;code&gt;NULL=false&lt;/code&gt; constraint to the new one, and then completely remove the old one, the data migration gem will make this process much easier compared to using a rake task.&lt;/p&gt;

&lt;p&gt;On the other hand, if the task is unrelated to database schema changes, a rake task might be a more suitable choice. It offers greater flexibility and testability, making it easier to manage tasks that are not directly tied to schema alterations.&lt;/p&gt;

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

&lt;p&gt;Throughout this article, we have explored various strategies for data migration, generation, and backfilling in a Rails application. We have implemented and improved these strategies, carefully considering their advantages and disadvantages. Additionally, we have compared two primary solutions, the rake task and data migration gem, and examined their suitability in different scenarios. &lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>patterns</category>
    </item>
    <item>
      <title>How to implement Adapter pattern in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Mon, 17 Jul 2023 07:44:20 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-implement-adapter-pattern-in-ruby-on-rails-1ibg</link>
      <guid>https://forem.com/vladhilko/how-to-implement-adapter-pattern-in-ruby-on-rails-1ibg</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to discuss the adapter pattern. We will explore what the adapter pattern is, why we need it, and how it can be implemented in a Rails application. We will also examine the benefits and drawbacks it provides. Additionally, we will provide three different examples to help better understand the concept and the reasons behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, the Adapter pattern allows you to transform the interface of a class into a different interface that clients expect. By doing so, it enables classes with incompatible interfaces to collaborate and work together seamlessly. We usually use the Adapter pattern for the following purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integrating incompatible components&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you need to integrate two components that have incompatible interfaces, Adapter can bridge the gap by providing a common interface that allows them to work together seamlessly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Implementing backward compatibility&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to make changes to an existing class or component but want to maintain compatibility with code that relies on the old interface, Adapter can be used to translate between the old and new interfaces.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Working with external libraries, services, or legacy systems&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When integrating with external libraries or services that have their own specific interfaces, Adapter can be used to adapt their interface to fit the interface expected by your application. Adapters are also particularly useful when working with legacy systems or third-party components that have outdated or incompatible interfaces.&lt;/p&gt;

&lt;p&gt;The Adapter pattern has the following pros and cons:&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enhanced flexibility&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Adapter pattern makes it easier to switch between implementations or integrate new components without modifying existing code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enhanced testability&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Adapter pattern enhances testability by isolating components, facilitating the use of mocking and dependency injection, and providing a clear interface for the adapted components.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encapsulation of complexity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Adapter pattern encapsulates complexity by simplifying complex logic, hiding implementation details, and separating concerns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independence from external solutions&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Adapter pattern enables you to switch or replace the external solution without affecting the rest of your codebase. This flexibility allows you to choose the most suitable external solution for your needs without the need for extensive modifications throughout your codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Potential increase in code complexity:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Introducing adapters can add complexity to the codebase, especially when dealing with a large number of adapters or complex adaptation logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Higher learning curve for developers:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers who are not familiar with the Adapter pattern may require additional time and effort to understand the purpose, design, and usage of adapters in the codebase. This learning curve can slow down development and onboarding processes for new team members.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Let's start by implementing our first adapter. We'll begin with a simple abstract example to help us grasp the concept. Afterward, we'll provide two additional examples and demonstrate how to integrate them into a Rails application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Cat and Dog
&lt;/h2&gt;

&lt;p&gt;In this example, we will discuss how two different objects with different interfaces can collaborate. Let's imagine that we have the following two classes:&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;Cat&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meow!&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s1"&gt;'Meow!'&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;Dog&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;woof!&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s1"&gt;'Woof!'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meow!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Meow!'&lt;/span&gt;
&lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;woof!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Woof!'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, let's say we would like to use one of them based on a certain condition, like the one shown below:&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;if&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test?&lt;/span&gt; 
  &lt;span class="no"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meow!&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;woof!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having many such conditions scattered throughout the code makes it difficult to maintain and less manageable. Therefore, we need to find a solution to address this issue. The main goal of the Adapter Pattern is to solve this problem by creating a common interface without altering the initial implementation. Let's explore how we can achieve it. &lt;/p&gt;

&lt;p&gt;Firstly, we need to create a new Adapter class for each class that we intend to adopt with a new interface.&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;CatAdapter&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak!&lt;/span&gt;
    &lt;span class="no"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meow!&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;DogAdapter&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak!&lt;/span&gt;
    &lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;woof!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;CatAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Meow!'&lt;/span&gt;
&lt;span class="no"&gt;DogAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Woof!'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have created a common interface for the two objects, which allows us to choose which one to use in a single location, without the need for additional changes throughout the application. For instance, we can implement the following code at the Rails configuration level:&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;AnimalAdapter&lt;/span&gt; &lt;span class="o"&gt;=&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;CatAdapter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;DogAdapter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can utilize &lt;code&gt;AnimalAdapter&lt;/code&gt; throughout the entire application, and this adapter will encapsulate the logic of Cat under the hood&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;AnimalAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speak!&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 'Meow!'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2. Temporary Data Storage
&lt;/h2&gt;

&lt;p&gt;In this example, we are going to implement a temporary data storage that should perform the following functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add the given data by key&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retrieve the data by key&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove the data by key&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such temporary data storage can be useful in various scenarios, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tracking the status of asynchronous jobs&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if we have a button that sends multiple emails, we can use temporary data storage to block the button and prevent duplicate sends until the job is finished and all emails are sent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storing intermediate results during data processing&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In data-intensive applications, temporary storage is often used to store intermediate results during data processing pipelines. This enables efficient data transformation, aggregation, or analysis before generating the final output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Temporary storage for any other purpose&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Temporary data storage can be employed for various other use cases where we need to temporarily store data.&lt;/p&gt;

&lt;p&gt;To demonstrate this adapter pattern, we will create three different implementations for each Rails environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the &lt;code&gt;Test&lt;/code&gt; environment, we will create a &lt;strong&gt;Memory-Based Temporary Data Storage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;Development&lt;/code&gt; environment, we will create an &lt;strong&gt;ActiveRecord-Based Temporary Data Storage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;Production&lt;/code&gt; environment, we will create a &lt;strong&gt;Redis-Based Temporary Data Storage&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's proceed with implementing each of them to gain a better understanding of the main purpose behind the adapter pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory-Based Temporary Data Storage
&lt;/h3&gt;

&lt;p&gt;The first solution will be the simplest one - we will create a Memory-Based Temporary Data Storage using a Ruby Hash. We will only use this solution for testing purposes, so we don't need to be concerned about data storage reliability.&lt;/p&gt;

&lt;p&gt;First, let's add a new adapter file and define the common interface:&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;# app/adapters/temporary_data_store_adapter/memory.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TemporaryDataStoreAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;For our data storage, we have defined three required methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's implement these methods:&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;# app/adapters/temporary_data_store_adapter/memory.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TemporaryDataStoreAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
      &lt;span class="vi"&gt;@store&lt;/span&gt; &lt;span class="o"&gt;=&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
      &lt;span class="s1"&gt;'OK'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

      &lt;span class="vi"&gt;@store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;Let's check how it actually works in the Rails 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 c&lt;/span&gt;

&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt;  #&amp;lt;TemporaryDataStoreAdapter::Memory:0x00000001166b1b80 @store={}&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "OK"&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;/p&gt;

&lt;p&gt;P.S. It also makes sense to add a clear method to remove all data from the Hash. You can run this command after every test that uses this adapter to avoid any global value problems in the specs:&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;clear&lt;/span&gt;
  &lt;span class="vi"&gt;@store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ActiveRecord-Based Temporary Data Storage
&lt;/h3&gt;

&lt;p&gt;Our second implementation of Temporary Data Storage utilizes ActiveRecord. I have mainly added this implementation to enhance our understanding of the Adapter concept, rather than for real-life usage.&lt;/p&gt;

&lt;p&gt;Firstly, to use ActiveRecord, we need to create a new model and generate a migration to set up the data storage. Let's create the following model:&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;# app/models/temporary_data_entry.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TemporaryDataEntry&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the corresponding migration:&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;# db/migrate/20230714142730_create_temporary_data_entries.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateTemporaryDataEntries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:temporary_data_entries&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&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;json&lt;/span&gt; &lt;span class="ss"&gt;:data&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;timestamps&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;index&lt;/span&gt; &lt;span class="ss"&gt;:key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&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;Afterward, execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, everything is set up, and we can proceed to create a new adapter. Let's take a look at how this adapter will be implemented:&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;# app/adapters/temporary_data_store_adapter/active_record.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TemporaryDataStoreAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveRecord&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;temp_data_entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&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;temp_data_entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
        &lt;span class="n"&gt;temp_data_entry&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;data: &lt;/span&gt;&lt;span class="n"&gt;data&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;TemporaryDataEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="s1"&gt;'OK'&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;TemporaryDataEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&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;data&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;TemporaryDataEntry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&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;delete&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;data&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;Let's test these methods in the 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 c&lt;/span&gt;


&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;TemporaryDataStoreAdapter::ActiveRecord:0x000000010ec64800&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "OK"&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis-Based Temporary Data Storage
&lt;/h3&gt;

&lt;p&gt;Our final implementation will be based on Redis. First of all, let's install the Redis gem and ensure that we are connected to the server. Add the following gem to your Gemfile:&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then execute:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Next, start the Redis server and verify if the connection is successful:&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;# REDIS_URL='redis://127.0.0.1:6379'&lt;/span&gt;
&lt;span class="c1"&gt;# rails c &lt;/span&gt;

&lt;span class="no"&gt;Redis&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;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"redis_version"=&amp;gt;"7.0.5" ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the Redis connection is successful. Now, let's proceed with adding a new Redis-based temporary data storage adapter:&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;# app/adapters/temporary_data_store_adapter/redis.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TemporaryDataStoreAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getdel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

      &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&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;redis&lt;/span&gt;
      &lt;span class="vi"&gt;@redis&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Redis&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;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REDIS_URL'&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;Let's check if it works:&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;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt;  #&amp;lt;TemporaryDataStoreAdapter::Redis:0x0000000112017c60&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;example: &lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "OK"&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"example"=&amp;gt;"example"}&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. All adapters have been successfully added. Now, let's take a look at how we can choose the one that is suitable for our needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize Adapter
&lt;/h3&gt;

&lt;p&gt;We're going to use different adapters depending on the Rails environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the &lt;code&gt;TEST&lt;/code&gt; environment, we'll use the Memory-Based Temporary Data Storage Adapter.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;DEVELOPMENT&lt;/code&gt; environment, we'll use the ActiveRecord-Based Temporary Data Storage Adapter.&lt;/li&gt;
&lt;li&gt;For the &lt;code&gt;PRODUCTION&lt;/code&gt; environment, we'll use the Redis-Based Temporary Data Storage Adapter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To solve this problem, we'll initialize each adapter in their respective environments in the &lt;code&gt;config/environments&lt;/code&gt; directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory-Based Temporary Data Storage Adapter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&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="c1"&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;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&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;temporary_data_store_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Memory&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ActiveRecord-Based Temporary Data Storage Adapter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&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="c1"&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;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&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;temporary_data_store_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis-Based Temporary Data Storage Adapter&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&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="c1"&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;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&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;temporary_data_store_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TemporaryDataStoreAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Redis&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;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;Once initialized, we can call this adapter like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# rails c&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temporary_data_store_adapter&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;TemporaryDataStoreAdapter::ActiveRecord:0x0000000114816ec8&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this doesn't look too convenient, so let's add a wrapper:&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;# app/adapters/adapter.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Adapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_adapter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
      &lt;span class="k"&gt;super&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;Now, we can call the adapter like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# rails c -e development&lt;/span&gt;

&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temporary_data_store&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;TemporaryDataStoreAdapter::ActiveRecord:0x0000000107634c98&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# rails c -e test&lt;/span&gt;

&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temporary_data_store&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;TemporaryDataStoreAdapter::Memory:0x00000001101743a8 @store={}&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;# rails c -e production&lt;/span&gt;

&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temporary_data_store&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; #&amp;lt;TemporaryDataStoreAdapter::Redis:0x0000000112ef9698&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. We now have an identical interface, and our previous incompatible solution becomes interchangeable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3. Datadog.
&lt;/h2&gt;

&lt;p&gt;In the last example, we will add an Abstract Monitoring Adapter to consolidate what we've already learned. Let's imagine that we have DataDog monitoring, and we want to disable it for the development and test environments. How would we do it? Let's create the following:&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;# app/adapters/monitoring_adapter/datadog.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MonitoringAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Datadog&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Send a real API request to DataDog'&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;And the Fake one:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MonitoringAdapter&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Fake&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Pretend that the request to DataDog has been sent'&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;Let's initialize them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fake&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&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="c1"&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;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&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;monitoring_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MonitoringAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Fake&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;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Datadog&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&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="c1"&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;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&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;monitoring_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MonitoringAdapter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Datadog&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;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;And let's check:&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 c -e production&lt;/span&gt;

&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monitoring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Send a real API request to DataDog&lt;/span&gt;

&lt;span class="c1"&gt;# rails c -e test&lt;/span&gt;

&lt;span class="no"&gt;Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monitoring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Pretend that the request to DataDog has been sent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

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

&lt;p&gt;In summary, the adapter pattern proves to be a valuable tool in Rails applications, enabling the integration of components with different interfaces without modifying existing code. By creating adapter classes for each component, we can achieve a common interface and easily switch between implementations. This approach enhances code maintainability, reduces complexity, and improves testability by isolating components and providing clear contracts. With adapters, developers can seamlessly integrate external libraries or services, adapt to legacy systems, and handle compatibility issues efficiently.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>patterns</category>
      <category>adapter</category>
    </item>
    <item>
      <title>How to add Feature Flags in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Wed, 12 Jul 2023 11:21:05 +0000</pubDate>
      <link>https://forem.com/vladhilko/managing-feature-release-strategies-with-feature-flags-in-ruby-on-rails-124b</link>
      <guid>https://forem.com/vladhilko/managing-feature-release-strategies-with-feature-flags-in-ruby-on-rails-124b</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to delve into the area of Feature Flags. We will explain what a feature flag is, why we need it, and how we can use it. We will discuss how to create feature flags and explore the advantages and disadvantages they offer. Additionally, we will explore different feature release strategies that can be safely employed in your Rails application. Finally, we will consider various improvements that can be applied to enhance the reliability of feature flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, a feature flag is a technique used in software development to enable or disable specific features at runtime. Feature flags act as switches that control the availability and visibility of certain features within an application without the need for redeploying or modifying the code. Using feature flags has its pros and cons, let's consider them:&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Risk Mitigation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Easily disable features in case of unexpected problems or errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A/B Testing and Experimentation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conduct experiments and compare different feature variations to make informed decisions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Controlled Rollouts&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Allows testing the feature on different platforms (staging/sandbox) before enabling it in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed of merging&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers can work on small pull requests that are merged frequently into the main code branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased Complexity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Increases code loading and cognitive load due to the need for additional conditional checks in the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Technical Debt&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If feature flags are not appropriately managed and cleaned up, they can become deprecated or unnecessary, resulting in increased code complexity and maintenance overhead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Testing Overhead&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The presence of multiple feature variations requires thorough testing of each flag's behavior and interactions, potentially increasing the testing effort and complexity.&lt;/p&gt;

&lt;p&gt;It's important to remember that a feature flag is a temporary solution that should be removed after the feature is fully implemented, tested, delivered, and approved by clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;We're going to use the &lt;a href="https://github.com/jnunemaker/flipper"&gt;flipper&lt;/a&gt; gem. Let's take a look at how to use it properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First of all, we need to install this gem.&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'flipper'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gem provides interfaces for the following most important features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enabling/Disabling the feature&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checking if the feature is enabled or not&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Showing the list of all features&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;
&lt;span class="c1"&gt;# #&amp;lt;Set: {}&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# #&amp;lt;Set: {#&amp;lt;Flipper::Feature:72040 name=:search, state=:on, enabled_gate_names=[:boolean], adapter=:memoizable&amp;gt;}&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disable&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# #&amp;lt;Set: {#&amp;lt;Flipper::Feature:72040 name=:search, state=:off, enabled_gate_names=[], adapter=:memoizable&amp;gt;}&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with the current approach is that the data stored in memory will be lost after reloading the Rails console. Therefore, we need to determine where exactly we want to store this data. Flipper provides several available options for storing the value, called &lt;a href="https://www.flippercloud.io/docs/adapters"&gt;adapters&lt;/a&gt;. In this case, we're going to use &lt;a href="https://www.flippercloud.io/docs/adapters/active-record"&gt;flipper-active_record&lt;/a&gt; as it is the most familiar one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add ActiveRecord adapter
&lt;/h3&gt;

&lt;p&gt;To use the ActiveRecord adapter, we need to install the gem:&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'flipper-active_record'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the gem is installed, we need to generate a migration where our data will be stored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g flipper:active_record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see how it works in the Rails 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 c &lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;
&lt;span class="c1"&gt;# #&amp;lt;Set: {}&amp;gt;&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# This command creates the following records in the database:&lt;/span&gt;

&lt;span class="c1"&gt;# [&lt;/span&gt;
&lt;span class="c1"&gt;#   #&amp;lt;Flipper::Adapters::ActiveRecord::Feature:0x000000011672da78&lt;/span&gt;
&lt;span class="c1"&gt;#   id: 1,&lt;/span&gt;
&lt;span class="c1"&gt;#   key: "search",&lt;/span&gt;
&lt;span class="c1"&gt;#   created_at: Sun, 09 Jul 2023 13:08:14.499774000 UTC +00:00,&lt;/span&gt;
&lt;span class="c1"&gt;#   updated_at: Sun, 09 Jul 2023 13:08:14.499774000 UTC +00:00&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;
&lt;span class="c1"&gt;# [&lt;/span&gt;
&lt;span class="c1"&gt;#   #&amp;lt;Flipper::Adapters::ActiveRecord::Gate:0x00000001166ddbb8&lt;/span&gt;
&lt;span class="c1"&gt;#   id: 1,&lt;/span&gt;
&lt;span class="c1"&gt;#   feature_key: "search",&lt;/span&gt;
&lt;span class="c1"&gt;#   key: "boolean",&lt;/span&gt;
&lt;span class="c1"&gt;#   value: "true",&lt;/span&gt;
&lt;span class="c1"&gt;#   created_at: Sun, 09 Jul 2023 13:08:14.527445000 UTC +00:00,&lt;/span&gt;
&lt;span class="c1"&gt;#   updated_at: Sun, 09 Jul 2023 13:08:14.527445000 UTC +00:00&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;
&lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Release Strategies
&lt;/h2&gt;

&lt;p&gt;Feature release strategies refer to the approaches and techniques used to release new features or updates in software development.&lt;/p&gt;

&lt;p&gt;Here are some common feature release strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Big Bang Release&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incremental Release&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature Flags/Toggles&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canary Release&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phased Rollout&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will discuss these strategies using a simple abstract example of changing the background color of our website to red. Let's take a closer look at each of them and provide examples of their implementation in Ruby.&lt;/p&gt;

&lt;h3&gt;
  
  
  Big Bang Release
&lt;/h3&gt;

&lt;p&gt;The Big Bang Release strategy states that all changes need to be deployed at once as a single, comprehensive update. This strategy does not require the use of feature flags. For example, if you had the following code in your application:&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;# change_background_color.rb&lt;/span&gt;

&lt;span class="no"&gt;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Big Bang Strategy will replace this code all at once wherever it is used, in a single move&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;# change_background_color.rb&lt;/span&gt;

&lt;span class="no"&gt;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Incremental Release
&lt;/h3&gt;

&lt;p&gt;The Incremental Release strategy states that you should deliver your code piece by piece to catch possible errors as soon as possible. For example, if we have the following code:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="no"&gt;ChangeBackgroundColor1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&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;# change_background_color_2.rb&lt;/span&gt;

&lt;span class="no"&gt;ChangeBackgroundColor2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Incremental Release Strategy suggests updating and deploying the first file first, and only after that, update and deploy the second one. For example, in the first deployment, we update the first file 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="c1"&gt;# change_background_color_1.rb&lt;/span&gt;

&lt;span class="no"&gt;ChangeBackgroundColor1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the second deployment, we update the second file 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="c1"&gt;# change_background_color_2.rb&lt;/span&gt;

&lt;span class="no"&gt;ChangeBackgroundColor2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, when we have multiple changes, they should be split and gradually updated and deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Flags/Toggles
&lt;/h3&gt;

&lt;p&gt;This is the first strategy where Feature Flags fit perfectly. In this strategy, we choose the behavior depending on whether the feature flag is enabled or not:&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;# change_background_color.rb&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt; &lt;span class="ss"&gt;:red_background_color&lt;/span&gt;
  &lt;span class="no"&gt;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&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;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It allows us to disable the feature if something goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canary Release
&lt;/h3&gt;

&lt;p&gt;The Canary Release Strategy allows us to enable a feature for specific percentages of users. This enables us to perform slow rollouts. For example, if we want to enable the feature only for one user, we can do the following:&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;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;first&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="c1"&gt;# =&amp;gt; false&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if we want to enable the &lt;code&gt;:red_background_color&lt;/code&gt; feature for only 25% of users, we need to do the following:&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;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_percentage_of_actors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="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;first&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="c1"&gt;# =&amp;gt; true/false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that you can read more about how the algorithm actually works &lt;a href="https://www.hackwithpassion.com/this-is-how-percentages-work-in-flipper/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phased Rollout
&lt;/h3&gt;

&lt;p&gt;The Phased Rollout Strategy allows us to enable a feature for specific groups, such as by country, role, or status etc. To enable this feature, we need to define the criteria or conditions for determining when the enabling should occur:&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;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;first&lt;/span&gt; 
&lt;span class="c1"&gt;# =&amp;gt; &amp;lt;User id: 1, role: "admin"&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admins&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;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:admins&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="c1"&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's consider another example where we want to enable the feature for all users from the USA. It would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="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;first&lt;/span&gt; 
&lt;span class="c1"&gt;# =&amp;gt; &amp;lt;User id: 1, country: "USA"&amp;gt;&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:from_usa&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;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;country&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'USA'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:from_usa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&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="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we have one problem. Where should we store &lt;code&gt;Flipper.register(:admins)&lt;/code&gt;? Since we don't save the value of the block in the database, this block should be placed somewhere at the configuration level. Let's add it to the initializer:&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;# config/initializers/flipper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admins&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;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'admin'&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;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fault Tolerance
&lt;/h3&gt;

&lt;p&gt;In this strategy, we run the new version of the code, but if something unexpected happens, we fallback to the previous version. It can be implemented 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;begin&lt;/span&gt;
  &lt;span class="no"&gt;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;UnexpectedError&lt;/span&gt;
  &lt;span class="no"&gt;ChangeBackgroundColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;Our solution is not perfect; there is still some room for improvement. Let's explore how we can enhance the following aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Removing the feature flag&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimizing requests using caching&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Providing a user interface (UI)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adding an API&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adding validations for Feature Flags&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Including information about the Feature Flags&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving Spec Performance&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Removing the feature flag
&lt;/h3&gt;

&lt;p&gt;It's important to remember that every feature flag is a temporary solution for safe rollouts, and sooner or later they should be removed from the codebase. How can we do it? To remove a Feature Flag, we need to follow these steps:&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;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optimizing requests using caching
&lt;/h3&gt;

&lt;p&gt;To optimize the retrieval of feature flag information and avoid unnecessary database calls, Flipper provides the option to enable caching. To implement caching, you need to install the &lt;code&gt;flipper-active_support_cache_store&lt;/code&gt; gem by adding it to your Gemfile:&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'flipper-active_support_cache_store'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;After adding the gem, you need to update the configuration file 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="c1"&gt;# config/initializers/flipper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper/adapters/active_record'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper/adapters/active_support_cache_store'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Flipper&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;adapter&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveSupportCacheStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MemoryStore&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;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;With this configuration in place, you can now check if a feature is enabled without triggering a database call for the next 5 minutes:&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;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# triggers a database call&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# does not trigger a database call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Providing a user interface (UI)
&lt;/h3&gt;

&lt;p&gt;To add a user interface (UI) for managing feature flags, you can follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;flipper-ui&lt;/code&gt; gem to your application's Gemfile:
&lt;/li&gt;
&lt;/ol&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'flipper-ui'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Execute the bundle command to install the gem:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update your config/routes.rb file to mount the Flipper UI:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;span class="no"&gt;YourRailsApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/flipper'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating the routes, the new UI will be available at &lt;a href="http://localhost:3000/flipper"&gt;http://localhost:3000/flipper&lt;/a&gt; (assuming your application is running on localhost and port 3000). You can find more information about using the Flipper UI &lt;a href="https://www.flippercloud.io/docs/ui"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding an API
&lt;/h3&gt;

&lt;p&gt;What if you want to expose the list of feature flags as an API? This can be useful when your client and API are separated, making it easier to retrieve the data. To add the Flipper API to your project, follow these steps:&lt;/p&gt;

&lt;p&gt;First, install the &lt;code&gt;flipper-api&lt;/code&gt; gem by adding it to your Gemfile:&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'flipper-api'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, execute the &lt;code&gt;bundle&lt;/code&gt; command to install the gem:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Update your &lt;code&gt;config/routes.rb&lt;/code&gt; file to include the Flipper 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="c1"&gt;# config/routes.rb&lt;/span&gt;

&lt;span class="no"&gt;YourRailsApp&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/flipper/api'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;With this configuration, you can make a GET request to &lt;a href="http://localhost:3000/flipper/api/features"&gt;http://localhost:3000/flipper/api/features&lt;/a&gt; to retrieve the list of feature flags. The response will be in JSON format, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"red_background_color"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"gates"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"actors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;

          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"actor"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"percentage_of_actors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"percentage_of_actors"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"percentage_of_time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"percentage_of_time"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"groups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;

          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"group"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more details on adding an API, refer to the Flipper documentation &lt;a href="https://www.flippercloud.io/docs/api"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding validations for Feature Flags
&lt;/h3&gt;

&lt;p&gt;As you may have noticed, our current implementation allows adding flags with any names and removing flags without restrictions. However, this approach is not reliable, as someone could make a typo and mistakenly enable/disable the wrong flag or accidentally remove a flag that is actively being used in production. Let's explore a possible solution to prevent such issues.&lt;/p&gt;

&lt;p&gt;First, we can create a value object to keep track of all available feature flags. This will help us ensure that only valid flags are used. Let's create the FeatureFlag value object:&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;# app/value_objects/feature_flag.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlag&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'red_background_color'&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;supported?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&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;This value object provides a list of all available feature flags and allows us to check if a specific flag is supported:&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;FeatureFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['search', 'red_background_color']&lt;/span&gt;
&lt;span class="no"&gt;FeatureFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supported?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can create a custom adapter that will enforce the availability of feature flags before enabling or adding them. The adapter can be implemented 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="c1"&gt;# lib/feature_flags/adapters/active_record_based.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;FeatureFlags&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Adapters&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveRecordBased&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;supported_feature_flag?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;super&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;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;supported_feature_flag?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;super&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;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;supported_feature_flag?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;super&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;supported_feature_flag?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;FeatureFlag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;supported?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this adapter, we check if the given feature flag is supported before performing actions such as adding, enabling, or removing it.&lt;/p&gt;

&lt;p&gt;To include this custom adapter in the Flipper configuration, we can update the initializer file:&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;# config/initializers/flipper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper/adapters/active_record'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Flipper&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;adapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;FeatureFlags&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecordBased&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="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, with these validations in place, the restrictions prevent performing actions with unsupported flags:&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;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_supported&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Including information about the Feature Flags
&lt;/h3&gt;

&lt;p&gt;As we mentioned earlier, feature flags should eventually be removed. It would be beneficial to have more information about each flag beyond just the name. Let's add additional fields to the feature flag value object, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected expiration date&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Owner&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this, we can update the FeatureFlag value object:&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;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureFlag&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;
      &lt;span class="p"&gt;[&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;'search'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Search description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;expected_expiration_date: &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mo"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mo"&gt;01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s1"&gt;'Backend team'&lt;/span&gt;
        &lt;span class="p"&gt;},&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;'red_background_color'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Red background color description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;expected_expiration_date: &lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mo"&gt;01&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;owner: &lt;/span&gt;&lt;span class="s1"&gt;'Frontend team'&lt;/span&gt;
        &lt;span class="p"&gt;}&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;supported?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag_name&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;Now, each feature flag includes additional information such as description, expected expiration date, and owner.&lt;/p&gt;

&lt;p&gt;Note: You can also consider &lt;a href="https://dev.to/vladhilko/say-goodbye-to-messy-constants-a-new-approach-to-moving-constants-away-from-your-model-58i1"&gt;defining constants in a YAML file&lt;/a&gt; to simplify the interface and keep the information organized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving Spec Performance
&lt;/h3&gt;

&lt;p&gt;To improve the performance of your tests, it is recommended to avoid unnecessary database hits when working with feature flags. You can achieve this by replacing the Active Record adapter with the Memory Adapter for your test environment.&lt;/p&gt;

&lt;p&gt;Here's what you need to do:&lt;/p&gt;

&lt;p&gt;Update your &lt;code&gt;config/initializers/flipper.rb file&lt;/code&gt; 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="c1"&gt;# config/initializers/flipper.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'flipper/adapters/active_record'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reloader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Flipper&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;adapter&lt;/span&gt; &lt;span class="k"&gt;do&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&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;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;With this configuration, the Memory Adapter will be used for the test environment, while the Active Record Adapter will be used for other environments.&lt;/p&gt;

&lt;p&gt;Now, when you run rails console in the test environment (&lt;code&gt;rails c -e test&lt;/code&gt;), you can verify that the database is not being triggered when working with feature flags:&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 c -e t&lt;/span&gt;

&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Flipper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:red_background_color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change will help improve the performance of your test suite by eliminating unnecessary database hits.&lt;/p&gt;

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

&lt;p&gt;In this article, we've been diving deep into the world of feature flags. We explored the benefits of using feature flags, including risk mitigation, A/B testing, controlled rollouts, and faster development cycles.&lt;/p&gt;

&lt;p&gt;We discussed different feature release strategies, such as the Big Bang Release, Incremental Release, Feature Flags/Toggles, Canary Release, Phased Rollout, and Fault Tolerance. Each strategy offers a unique approach to releasing new features or updates, providing flexibility and control over the rollout process.&lt;/p&gt;

&lt;p&gt;To enhance our feature flag implementation, we made several improvements. We added validations to ensure that only supported flags can be enabled or added, preventing potential errors. We integrated caching to optimize performance by reducing unnecessary database hits. Additionally, we explored the options of providing a user interface and adding an API to expose feature flag information to clients.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>How to add Concern in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Fri, 07 Jul 2023 15:56:45 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-add-concern-in-ruby-on-rails-3en9</link>
      <guid>https://forem.com/vladhilko/how-to-add-concern-in-ruby-on-rails-3en9</guid>
      <description>&lt;h2&gt;
  
  
  Overview:
&lt;/h2&gt;

&lt;p&gt;In this article, we will discuss the usage of &lt;code&gt;ActiveSupport::Concern&lt;/code&gt;. We will delve into what it is, why we need it, and the problems it can solve. Additionally, we will explore the pros and cons associated with its usage. To illustrate its practical implementation, we will provide three examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition:
&lt;/h2&gt;

&lt;p&gt;In simple terms, &lt;code&gt;Concern&lt;/code&gt; allows you to extract common logic from different models into a reusable module. You may ask, "Why can't we just use a module? Why do we need to define something else?" Here are the main differences: Essentially, &lt;code&gt;Concern&lt;/code&gt; is an ordinary module, but it allows you to include any &lt;code&gt;ActiveRecord&lt;/code&gt; behaviors that you can define on the model. Therefore, we can consider it as a module for encapsulating and reusing &lt;code&gt;ActiveRecord&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code Reusability:&lt;/strong&gt; ActiveSupport Concern allows us to follow the DRY principle by extracting common logic into reusable modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex Dependencies:&lt;/strong&gt; Overusing concerns can result in a complex web of dependencies, making the code harder to understand and maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficulty in Code Search:&lt;/strong&gt; Finding the definition of a specific behavior or functionality can be more challenging due to the dispersed nature of concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In essence, concerns are well-suited for storing technical components, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding file helpers to facilitate handling files with a clear interface.&lt;/li&gt;
&lt;li&gt;Creating wrappers for existing attributes and values to enhance interface clarity.&lt;/li&gt;
&lt;li&gt;Implementing technical features like change history tracking, logging, and more.&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, concerns are not well suitable for storing business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;In the following section, we will discuss the key elements of concerns. To understand concerns, we only need to know what a module is and the &lt;code&gt;included&lt;/code&gt; and &lt;code&gt;class_methods&lt;/code&gt; methods from &lt;code&gt;ActiveSupport::Concern&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Module
&lt;/h3&gt;

&lt;p&gt;A module in Ruby is a container that groups together related methods, constants, and other module or class definitions. It provides a way to organize and reuse code in a modular manner. And Сoncern is just a module, so we need to know how to create it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/concerns/concern_sample.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ConcernSample&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello_world&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Hello World!'&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;h3&gt;
  
  
  ActiveSupport::Concern methods
&lt;/h3&gt;

&lt;p&gt;So, what is the main difference between a simple Ruby module and a concern? The main differences are that when you include &lt;code&gt;ActiveSupport::Concern&lt;/code&gt; in a module, two new methods are added. These methods are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;included&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method enables us to add Active Record logic directly to the module and reuse it at the model level. For 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;# app/models/concerns/concern_sample.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ConcernSample&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Define class methods here&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;my_class_method&lt;/span&gt;
      &lt;span class="c1"&gt;# Class method logic&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Define instance methods here&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_instance_method&lt;/span&gt;
      &lt;span class="c1"&gt;# Instance method logic&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Define associations here&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:comments&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;

    &lt;span class="c1"&gt;# Define validations here&lt;/span&gt;
    &lt;span class="n"&gt;validates_presence_of&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
    &lt;span class="n"&gt;validates_uniqueness_of&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;

    &lt;span class="c1"&gt;# Define callbacks here&lt;/span&gt;
    &lt;span class="n"&gt;before_save&lt;/span&gt; &lt;span class="ss"&gt;:do_something&lt;/span&gt;
    &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:do_something_else&lt;/span&gt;

    &lt;span class="c1"&gt;# Define scopes here&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&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;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:recent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&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;ul&gt;
&lt;li&gt;&lt;code&gt;class_methods&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method allows us to add methods that can be called from the class itself:&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;# app/models/concerns/concern_sample.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ConcernSample&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_class_method&lt;/span&gt;
      &lt;span class="c1"&gt;# Class method logic&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;Let's consider three examples to gain a better understanding of this concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: FullDescription.
&lt;/h2&gt;

&lt;p&gt;In the first example, we will consider a very basic scenario to gain an understanding of how this actually works. For instance, let's assume we have the following model:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we decided to add a method to the model that would provide the full description of this animal.&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full_description&lt;/span&gt;
    &lt;span class="s2"&gt;"Name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Kind: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Status: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&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;Here's how it works:&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Name: Simba. Kind: Cat. Status: Adopted"&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;second&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Name: Rocky. Kind: Rabbit. Status: Available"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do we need to do if we want to move this logic to the concern? First of all, we need to create a new concern:&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;# app/models/concerns/full_description.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;FullDescription&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full_description&lt;/span&gt;
    &lt;span class="s2"&gt;"Name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Kind: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Status: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&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;And include this concern at the model level:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;FullDescription&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the same interface is still available:&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Name: Simba. Kind: Cat. Status: Adopted"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We didn't include &lt;code&gt;ActiveSupport::Concern&lt;/code&gt; here just to highlight that for simple methods without &lt;code&gt;ActiveRecord&lt;/code&gt; logic, it will still work fine. However, it will also work with &lt;code&gt;ActiveSupport::Concern&lt;/code&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;# app/models/concerns/full_description.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;FullDescription&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full_description&lt;/span&gt;
      &lt;span class="s2"&gt;"Name: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Kind: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Status: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;status&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Name: Simba. Kind: Cat. Status: Adopted."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to add class methods, we can do the following:&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;# app/models/concerns/full_description.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;FullDescription&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;full_description&lt;/span&gt;
      &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;Now the &lt;code&gt;full_description&lt;/code&gt; method is available on the &lt;code&gt;Animal&lt;/code&gt; 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="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_description&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "Name: Simba. Kind: Cat. Status: Adopted. Name: Rocky. Kind: Rabbit. Status: Available."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2: UUID
&lt;/h2&gt;

&lt;p&gt;In the second example, we will create a concern that generates and saves a unique UUID value before creating a new database record. To achieve this, we will define a new concern 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="c1"&gt;# app/models/concerns/uuid.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;UUID&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before_create&lt;/span&gt; &lt;span class="ss"&gt;:set_uuid&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;set_uuid&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;uuid&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&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;And we will include it at the model level:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, every time we create a new Animal record in the database, the UUID will be automatically generated for us:&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; '7f934646-bc67-446c-920d-ca5415d28b94'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;P.S. To use the UUID acronym, you need to add the following code to &lt;code&gt;config/initializers/inflections.rb&lt;/code&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;# config/initializers/inflections.rb&lt;/span&gt;

&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Inflector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inflections&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;inflect&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;inflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acronym&lt;/span&gt; &lt;span class="s1"&gt;'UUID'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 3: PredicateMethods
&lt;/h2&gt;

&lt;p&gt;In the last example, let's consider adding a concern that allows us to simplify the interface for attributes with a fixed number of available values. For instance, if an &lt;code&gt;animal&lt;/code&gt; record has a &lt;code&gt;status&lt;/code&gt; attribute with the following available values: &lt;code&gt;['available', 'adopted', 'pending']&lt;/code&gt;, then we would like to have the following interface:&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s1"&gt;'available'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;adopted?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false &lt;/span&gt;

&lt;span class="c1"&gt;# or with prexix&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_available?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_adopted?&lt;/span&gt; 
&lt;span class="c1"&gt;# =&amp;gt; flase&lt;/span&gt;

&lt;span class="c1"&gt;# etc ..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do we need to do to implement it? Let's create a new concern called &lt;code&gt;PredicateMethods&lt;/code&gt; and add its method to the model:&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;# app/models/animal.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;PredicateMethods&lt;/span&gt;

  &lt;span class="n"&gt;define_predicate_methods&lt;/span&gt; &lt;span class="ss"&gt;attribute: :status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="ss"&gt;available_values: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'available'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'adopted'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                           &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how is this concern implemented? It's quite simple. We just need to define a new method called &lt;code&gt;define_predicate_methods&lt;/code&gt;, and this method will generate new methods on the model for every available value that you pass as an argument. Inside this method, we will check if the current status value is equal to the given value or not. Here's the 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="c1"&gt;# app/models/concerns/predicate_methods.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;PredicateMethods&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="no"&gt;MethodAlreadyDefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ArgumentError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;define_predicate_methods&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;available_values&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;prefix: &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;available_values&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;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;method_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'?'&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="nf"&gt;to_sym&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;MethodAlreadyDefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already defined"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;instance_methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;value&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our interface works as desired:&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;adopted?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to add the &lt;code&gt;status&lt;/code&gt; prefix, you just need to change the value of the &lt;code&gt;prefix&lt;/code&gt; attribute from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_available?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;

&lt;span class="n"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status_adopted?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now we can add this concern to any of our models and make their interfaces more readable and simplified.&lt;/p&gt;

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

&lt;p&gt;In conclusion, in this article we delved into the usage of &lt;code&gt;ActiveSupport::Concern&lt;/code&gt; in Rails. We explored what it was, why we needed it, and the problems it could solve. We examined the pros and cons of utilizing this approach, considering different perspectives. Furthermore, to enhance our understanding of the concept, we provided three examples that illustrated its practical implementation.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Managing Platform-Specific Features in Rails: The Right Way to Customize App Behavior</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Wed, 05 Jul 2023 11:07:39 +0000</pubDate>
      <link>https://forem.com/vladhilko/managing-platform-specific-features-in-rails-the-right-way-to-customize-app-behavior-1nc9</link>
      <guid>https://forem.com/vladhilko/managing-platform-specific-features-in-rails-the-right-way-to-customize-app-behavior-1nc9</guid>
      <description>&lt;h2&gt;
  
  
  Overview:
&lt;/h2&gt;

&lt;p&gt;In this article, we will discuss the usage of Platform Settings in a Rails application. We will explore why we need them and the specific problems they aim to solve. Additionally, we will consider four different available options, examining the pros and cons of each. Furthermore, we will fully implement the most suitable approach and explore how to ensure the quality of platform settings through contracts and tests. By the end of the article, we will have a production-ready solution to this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition:
&lt;/h2&gt;

&lt;p&gt;In simple terms, platform settings in a Rails application allow us to configure different behaviors for various platforms within the same application. To provide clarity, let's consider three different examples.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Example 1: McDonald's Franchise&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's imagine that you're creating an application for a McDonald's franchise. After the initial version is ready, they approach you with a request for adjustments specific to another country. These adjustments may include a different menu, enabling specific promotional actions, implementing logic for alcohol sales, and so on. You successfully implement these changes. However, they approach you again, this time requesting adjustments for a third customer who has purchased their own franchise. This scenario prompts you to seek a more generalized solution, as potentially every franchise could have hundreds of differences, and the number of franchise customers can grow rapidly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Example 2: Betting Sites&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's consider the example of a betting site. You have created the initial fully functional version of the site. However, after some time, a customer approaches you with a request for changes because they intend to run the site on a different platform in another market with distinct regulatory rules. These changes may include implementing rules that prohibit registration for individuals under 18, sending information about profits to the government, and enforcing restrictions for addicted gamblers, among other requirements. With numerous new markets and diverse regulations, you realize the necessity of developing a robust solution to handle these various issues and simplify your workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Example 3: Digital Banking Service&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's explore the example of digital banking services. You have developed a bank application that functions effectively within your local market. However, you recognize the opportunity to offer this banking service to other customers, enabling them to create their own banks using your core platform. Consequently, you realize the need to introduce logic that allows for flexibility in accommodating different customers. This realization prompts you to implement platform settings within your application, ensuring the adaptability and customization required to support various banking institutions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exploring Implementation Options: Choosing the Right Approach
&lt;/h2&gt;

&lt;p&gt;In this chapter, we will explore four different possible approaches to solving the problems discussed in the previous examples. Let's examine each of them in detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option 1. Create a Separate Branch to Implement the New Logic&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 2. Applying Conditional Operators&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 3. Setting Up a Database Configuration Table&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 4. Adding Platform Settings YAML File Configurations&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 1. Create a Separate Branch to Implement the New Logic
&lt;/h3&gt;

&lt;p&gt;One possible solution that might initially come to mind is to create a separate branch for each new platform and make all the necessary changes there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to implement additional logic to support platform differentiation.&lt;/li&gt;
&lt;li&gt;Code remains clear and does not include unnecessary conditional operators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Synchronization with the core platform:

&lt;ul&gt;
&lt;li&gt;Resolving conflicts becomes a complex task when changes overlap in the same location.&lt;/li&gt;
&lt;li&gt;There is a high risk of introducing errors or disrupting functionality.&lt;/li&gt;
&lt;li&gt;Maintenance becomes more challenging.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2. Applying Conditional Operators
&lt;/h3&gt;

&lt;p&gt;The second solution that you can consider is to create multiple &lt;code&gt;if/else&lt;/code&gt; conditions directly in the code. For example, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Platform A"&lt;/span&gt;
  &lt;span class="c1"&gt;# Platform A specific code&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Platform B"&lt;/span&gt;
  &lt;span class="c1"&gt;# Platform B specific code&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Platform C"&lt;/span&gt;
  &lt;span class="c1"&gt;# Platform C specific code&lt;/span&gt;
&lt;span class="c1"&gt;# Add more conditions for additional platforms as needed&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# Default code for unsupported platforms&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;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Straightforward implementation without the need for separate branches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code complexity and readability:

&lt;ul&gt;
&lt;li&gt;The code can become cluttered and harder to understand as the number of platforms and conditions grows.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Maintenance challenges:

&lt;ul&gt;
&lt;li&gt;Modifying or adding platform-specific logic requires modifying the main codebase, which can introduce errors and make maintenance more difficult.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Lack of flexibility:

&lt;ul&gt;
&lt;li&gt;Adding or removing platforms requires modifying the code, potentially leading to more significant changes and potential regressions.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 3. Setting Up a Database Configuration Table
&lt;/h3&gt;

&lt;p&gt;The third option is to create a separate table to store all configurations. For example, if we have platform settings for the admin page, it might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;platform_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PlatformSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;admin_enabled: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="ss"&gt;admin_show_page_1: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;admin_show_page_2: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;admin_available_pages: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'companies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'configurations'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;platform_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin_enabled&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin_show_page_1&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin_show_page_2&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin_available_pages&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['system', 'companies', 'configurations']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides a user-friendly interface for managing platform settings.&lt;/li&gt;
&lt;li&gt;Offers ease of modification and updates to platform settings configurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct dependency on the database:

&lt;ul&gt;
&lt;li&gt;Relying solely on the database for configuration settings may not be the most efficient approach, especially if the settings only need to be set once during the platform's launch.&lt;/li&gt;
&lt;li&gt;Overreliance on the database can potentially impact performance.&lt;/li&gt;
&lt;li&gt;In some cases, it may be necessary to check settings before establishing a database connection.&lt;/li&gt;
&lt;li&gt;Increases complexity in writing tests.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 4. Adding Platform Settings YAML File Configurations
&lt;/h3&gt;

&lt;p&gt;The last option, which we are going to implement, is a configuration YAML file. We will create multiple &lt;code&gt;.yml&lt;/code&gt; files, similar to this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;first_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;second_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;third_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And transform them into an appropriate interface, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides a clear and accessible interface for working with platform-specific configurations.&lt;/li&gt;
&lt;li&gt;Separates configurations from the business logic, consolidating them in one central location.&lt;/li&gt;
&lt;li&gt;Offers flexibility for extending and modifying configurations as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires additional logic to load and parse the YAML files.&lt;/li&gt;
&lt;li&gt;Potential for inconsistencies if the YAML files and the codebase are not synchronized.&lt;/li&gt;
&lt;li&gt;Platform setting changes can only be performed by developers, restricting customer access to configuration modifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's examine this option more precisely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding Platform Settings YAML File Configurations. Implementation.
&lt;/h2&gt;

&lt;p&gt;In this chapter, we will explore the implementation of platform settings using a YAML configuration file. As always, we will start with the simplest solution and gradually enhance it. We will begin by implementing the initial solution and then progress to make it more flexible and convenient. Additionally, we will incorporate logic to ensure the validity of our configuration files and explore options for writing specifications for these settings.&lt;/p&gt;

&lt;p&gt;Here is our plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Creating the basic solution&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving the basic solution&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adding contracts to guarantee integrity and synchronization&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrating the solution with tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating the basic solution
&lt;/h2&gt;

&lt;p&gt;Let's explore the basic solution and outline the steps required for its implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 1: Add a new environment variable that indicates the current platform.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 2: Add YAML files containing configurations for each platform.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 3: Develop the necessary code to handle the YAML files and create a suitable interface.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's begin the implementation process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Add a new environment variable that indicates the current platform.
&lt;/h4&gt;

&lt;p&gt;We need to add &lt;code&gt;CURRENT_PLATFORM&lt;/code&gt; to the list of environment variables.&lt;br&gt;
&lt;/p&gt;

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

CURRENT_PLATFORM='first_platform'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Add YAML files containing configurations for each platform.
&lt;/h4&gt;

&lt;p&gt;Let's examine the platform settings for the admin, as mentioned earlier, across the three available platforms: &lt;code&gt;first_platform&lt;/code&gt;, &lt;code&gt;second_platform&lt;/code&gt;, and &lt;code&gt;third_platform&lt;/code&gt;. To accomplish this, we need to create a dedicated file named &lt;code&gt;config/settings/platform/admin.yml&lt;/code&gt;. In this file, we will define the configuration options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings/platform/admin.yml&lt;/span&gt;

&lt;span class="na"&gt;first_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;available_pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;companies&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;configurations&lt;/span&gt;
&lt;span class="na"&gt;second_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;show_page_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;available_pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
&lt;span class="na"&gt;third_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;show_page_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;available_pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, let's create a second file for the sake of clarity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/settings/platform/animal.yml&lt;/span&gt;

&lt;span class="na"&gt;first_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;second_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;third_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Develop the necessary code to handle the YAML files and create a suitable interface.
&lt;/h4&gt;

&lt;p&gt;My objective is to load the content from the YAML files and retrieve data exclusively for the current platform (&lt;code&gt;first_platform&lt;/code&gt;). The expected interface should resemble the following:&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;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"enabled"=&amp;gt;true, "show_page_1"=&amp;gt;true, "show_page_2"=&amp;gt;true, "available_pages"=&amp;gt;["system", "companies", "configurations"]}&lt;/span&gt;
&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"enabled"=&amp;gt;true}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's explore the necessary steps for implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1:&lt;/strong&gt; Retrieve a list of all files from the &lt;code&gt;config/settings/platform&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; Iterate through the obtained list and define a class method for each file within the &lt;code&gt;Settings::Platform::Repository&lt;/code&gt;, such as &lt;code&gt;Settings::Platform::Repository.admin&lt;/code&gt;, &lt;code&gt;Settings::Platform::Repository.animal&lt;/code&gt;, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3:&lt;/strong&gt; Ensure that the method created in &lt;code&gt;Step 2&lt;/code&gt; returns the YAML file's hash content exclusively for the current platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4:&lt;/strong&gt; Execute these steps within the Rails initializer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the code 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="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Settings&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Platform&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Repository&lt;/span&gt;

      &lt;span class="no"&gt;CONFIG_DIRECTORY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'config/settings/platform'&lt;/span&gt;
      &lt;span class="no"&gt;CONFIG_FILE_EXTENSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'.yml'&lt;/span&gt;
      &lt;span class="no"&gt;DEFAULT_PLATFORM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'first_platform'&lt;/span&gt;

      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load!&lt;/span&gt;
          &lt;span class="n"&gt;all_platform_settings_config_files&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;file_path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;define_platform_setting_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="ss"&gt;method_name: &lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CONFIG_FILE_EXTENSION&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;current_platform&lt;/span&gt;
          &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CURRENT_PLATFORM'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DEFAULT_PLATFORM&lt;/span&gt;&lt;span class="p"&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;all_platform_settings_config_files&lt;/span&gt;
          &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&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="no"&gt;CONFIG_DIRECTORY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"*&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CONFIG_FILE_EXTENSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;define_platform_setting_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
          &lt;span class="n"&gt;deep_freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current_platform&lt;/span&gt;&lt;span class="p"&gt;]&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;deep_freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;deep_freeze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I have added a &lt;code&gt;load!&lt;/code&gt; method to facilitate the initialization process, and I have also included a &lt;code&gt;deep_freeze&lt;/code&gt; method to prevent modifications to the hash after initialization.&lt;/p&gt;

&lt;p&gt;Let's verify if it works as intended. We will execute the &lt;code&gt;load!&lt;/code&gt; method within the Rails configuration &lt;code&gt;before_initialize&lt;/code&gt; block, as illustrated below:&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;# config/initializers/platform_settings.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'settings/platform/repository'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load!&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 take a look at the updated interface:&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;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'show_page_1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  What can we improve in the interface described above?
&lt;/h4&gt;




&lt;p&gt;There are two main problems with the current implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Problem 1: Requiring the full name every time&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problem 2: Unoptimized interface with hash usage&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's describe them more precisely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: Requiring the full name every time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the given example, we have to use Settings::Platform::Repository every time we want to access the platform configuration. This can become cumbersome and repetitive, requiring us to create helper methods each time, such as:&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;platform_admin&lt;/span&gt;
  &lt;span class="vi"&gt;@platform_admin&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'show_page_1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it can be quite cumbersome and tedious to define these helper methods every time, particularly when dealing with numerous platform settings. It would be ideal if we could find a solution that eliminates the need for creating such helpers altogether.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2:Unoptimized interface with hash usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current implementation utilizes a hash-based interface for accessing the platform settings, which can make the code less readable and more error-prone.&lt;/p&gt;

&lt;p&gt;So, what can we do to address this issue? What kind of interface would be more desirable? Ideally, we would like to have a simplified and intuitive interface that allows us to access the platform settings without the need for explicit helper methods. Here's an example of the desired interface:&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;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_page_1?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true &lt;/span&gt;
&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_pages&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['system', 'companies', 'configurations']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, I prefer not to have a global variable accessible throughout the entire application. Instead, I would like to include these settings only in the specific context where I actually intend to use them, similar to how we utilize modules or mixins. Therefore, my desired interface should look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's delve into the implementation of this approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improving the basic solution
&lt;/h2&gt;




&lt;p&gt;How can we achieve the desired interface above? What do we need? Let's take a closer look. We need to make three improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improvement 1: Including the module with all platform settings helpers inside.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improvement 2: Returning an object with the appropriate interface instead of a hash.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improvement 3: Combining Improvements 1 and 2 together.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Improvement 1: Including the module with all platform settings helpers inside.
&lt;/h3&gt;

&lt;p&gt;Let's consider the following code. In this code, we create a module with our helpers inside so that we can include it instead of defining new helpers every time.&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;module&lt;/span&gt; &lt;span class="nn"&gt;PlatformAdmin&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;platform_admin&lt;/span&gt;
    &lt;span class="vi"&gt;@platform_admin&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;PlatformAdmin&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this approach is not flexible; we would have to create too many modules for every platform setting file. I'd like these modules to be created automatically just by the platform setting name. How can we do it? Actually, it's quite simple. We need to define a method that will create a new module instance based on the platform name and dynamically define methods inside. Here's how it may look:&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;# lib/settings/platform/mixin.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Settings&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Platform&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Mixin&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;platform_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;Module&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;define_method&lt;/span&gt; &lt;span class="s2"&gt;"platform_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="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;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 we can do the following:&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;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;platform_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;platform_admin&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {"show_profit_calculation_page"=&amp;gt;true}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of creating a helper method every time, we can include them via the module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improvement 2: Returning an object with the appropriate interface instead of a hash.
&lt;/h3&gt;

&lt;p&gt;Returning the raw hash from the configuration doesn't seem convenient. So let's change it and return a new object that transforms the hash into an object with the appropriate interface. To achieve this, we need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a separate class that accepts the platform settings name and performs the following actions:

&lt;ul&gt;
&lt;li&gt;Loads the platform setting hash by name&lt;/li&gt;
&lt;li&gt;Iterates through every hash key&lt;/li&gt;
&lt;li&gt;Defines suitable methods instead of fetch for every hash key&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlatformStruct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;SimpleDelegator&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;generate_methods&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;generate_methods&lt;/span&gt;
    &lt;span class="n"&gt;keys&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;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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;And now we can do the following:&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;platform_struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PlatformStruct&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="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;platform_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true &lt;/span&gt;
&lt;span class="n"&gt;platform_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_pages&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["system", "companies", "configurations"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Improvement 3: Combining Improvements 1 and 2 together.
&lt;/h3&gt;

&lt;p&gt;The last step is to combine Improvements 1 and 2 together. We will also change the name from &lt;code&gt;platform_module&lt;/code&gt; to &lt;code&gt;[]&lt;/code&gt; to make it more readable. Instead of using the &lt;code&gt;Settings::Platform::Repository&lt;/code&gt; class, we will use the &lt;code&gt;PlatformStruct&lt;/code&gt; class from &lt;code&gt;Improvement 2&lt;/code&gt;. Here's the updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/settings/platform/mixin.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Settings&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Platform&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Mixin&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;Module&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;define_method&lt;/span&gt; &lt;span class="s2"&gt;"platform_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
            &lt;span class="no"&gt;PlatformStruct&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlatformStruct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;SimpleDelegator&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

          &lt;span class="n"&gt;generate_methods&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;generate_methods&lt;/span&gt;
          &lt;span class="n"&gt;keys&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;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;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;And here's our new interface:&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;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:animal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;platform_animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding contracts to guarantee integrity and synchronization
&lt;/h2&gt;

&lt;p&gt;There's still one problem with our approach. What happens if someone makes a mistake and adds incorrect configuration? For example, if the configuration has a different structure or incorrect data types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;first_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;available_pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;companies&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;configurations&lt;/span&gt;
&lt;span class="na"&gt;second_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yes'&lt;/span&gt; &lt;span class="c1"&gt;# Incorrect data type&lt;/span&gt;
  &lt;span class="na"&gt;show_page_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;no'&lt;/span&gt;  &lt;span class="c1"&gt;# Incorrect data type&lt;/span&gt;
&lt;span class="na"&gt;third_platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;show_page_1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;enabled'&lt;/span&gt; &lt;span class="c1"&gt;# Incorrect data type&lt;/span&gt;
  &lt;span class="na"&gt;available_pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Text'&lt;/span&gt; &lt;span class="c1"&gt;# Incorrect data type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not appropriate, and we need to ensure that the structure is the same for every platform and that all attributes are mirrored and have valid types. To achieve this, we can use the &lt;a href="https://github.com/dry-rb/dry-validation"&gt;dry-validation&lt;/a&gt; gem, which provides a powerful validation library for Ruby.&lt;/p&gt;

&lt;p&gt;To install the &lt;code&gt;dry-validation&lt;/code&gt; gem, you can add it to your Gemfile and run bundle install. Here's an example of how to install it:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'dry-validation'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Contract implementation
&lt;/h3&gt;

&lt;p&gt;Once the gem is installed, we can use it to define a schema for validating the platform settings configuration. The schema will specify the expected structure, attribute types, and any other validation rules.&lt;/p&gt;

&lt;p&gt;Here's an example of how we can use &lt;code&gt;dry-validation&lt;/code&gt; to define a schema and validate the platform settings configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Admin&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/settings/platform/contracts/admin.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Settings&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Platform&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Contracts&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Admin&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:enabled&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:show_page_1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:show_page_2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:available_pages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;included_in?: &lt;/span&gt;&lt;span class="sx"&gt;%w[system companies configurations]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Animal&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/settings/platform/contracts/animal.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Settings&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Platform&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Contracts&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contract&lt;/span&gt;

        &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:enabled&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After we have defined our contracts, let's test how they actually work by validating some sample data against the schema. Here's 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="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contracts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Admin&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;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;show_page_1: &lt;/span&gt;&lt;span class="s1"&gt;'enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;available_pages: &lt;/span&gt;&lt;span class="s1"&gt;'Text'&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; { :enabled=&amp;gt;["is missing"], :show_page_1=&amp;gt;["must be boolean"], :show_page_2=&amp;gt;["is missing"], :available_pages=&amp;gt;["must be an array"] }&lt;/span&gt;

&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contracts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Animal&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;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;enabled: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; {} &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we pass a hash as an argument to the contract, and the contract checks the hash against its defined conditions. If any errors or inconsistencies are found, it will return a result object containing the specific error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract validations
&lt;/h3&gt;

&lt;p&gt;To ensure that the configuration data adheres to the defined contracts, we can create a test spec that iterates over all platform settings files and performs the contract validation. Here's how the spec may look:&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;# spec/lib/settings/platform/contracts_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&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;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'spec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lib'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'settings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'contracts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'**'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.rb'&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:require&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Contracts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config/settings/platform/*.yml'&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;settings_file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'first_platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'second_platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'third_platform'&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;platform&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"when checking &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;settings_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; for &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;contract_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_settings_content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:contract_class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.yml'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;camelize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constantize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:platform_settings_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings_file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deep_symbolize_keys&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_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;span class="n"&gt;context&lt;/span&gt; &lt;span class="s1"&gt;'when checking existing spec files'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:spec_contract_file_name_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&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;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'spec'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lib'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'settings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'contracts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.rb'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:platform_settings_file_name_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&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;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'settings'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'platform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.yml'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.yml'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'every contract is expected to have a matching platform settings file'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spec_contract_file_name_list&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_settings_file_name_list&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;That's it. Now we can be confident that the structure and content of our platform settings are valid. The last aspect I want to highlight is writing specs and manipulating these settings. We aim to make our tests as flexible and straightforward as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating the solution with tests
&lt;/h2&gt;

&lt;p&gt;There are two strategies that we can consider during testing to manipulate the platform settings and obtain different content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Option 1. Stubbing Platform Settings with Custom Values&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Option 2: Overriding ENV Variable to Use Content from Another Platform&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 1. Stubbing Platform Settings with Custom Values
&lt;/h3&gt;

&lt;p&gt;In this approach, we can use stubbing techniques to override the behavior of the platform settings and return custom values. This allows us to simulate different scenarios and test our code under various configurations. For example, we can stub a specific setting to return a different value than what is defined in the actual configuration file. To make this work, we are going to define a shared context with the following interface:&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;include_context&lt;/span&gt; &lt;span class="s1"&gt;'when platform settings are'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;admin: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'enabled'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'show_page_1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'available_pages'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how we can use it in the spec:&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;# spec/lib/settings/platform/sample_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Sample'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'#platform_admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:platform_admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;klass&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;platform_admin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Class&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;include_context&lt;/span&gt; &lt;span class="s1"&gt;'when platform settings are'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;admin: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s1"&gt;'enabled'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'show_page_1'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'available_pages'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'system'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns correct platform specific values'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_page_1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_page_1?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_pages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'system'&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;In the shared context definition, we stubbed &lt;code&gt;Settings::Platform::Repository&lt;/code&gt; by replacing it with a custom hash value that is sent as an argument in the spec.&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;# spec/support/shared_contexts/platform_settings.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shared_context&lt;/span&gt; &lt;span class="s1"&gt;'when platform settings are'&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;name_and_settings&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;name_and_settings&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings_data&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings_data&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;Also, don't forget to include this shared context in &lt;code&gt;spec/rails_helper.rb&lt;/code&gt;. This ensures that the shared context is available for all specs in your Rails application.&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;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'support/shared_contexts/platform_settings'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Now we can test more easily and not depend on the settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Overriding ENV Variable to Use Content from Another Platform
&lt;/h3&gt;

&lt;p&gt;Another strategy is to override the ENV variable that determines the current platform. By changing the value of the ENV variable, we can force the code to load platform settings from a different platform's configuration file. This approach allows us to test how our code behaves with different platform settings without modifying the actual configuration files. We'll achieve this by utilizing the &lt;a href="https://github.com/thoughtbot/climate_control"&gt;Climate Controle&lt;/a&gt; gem. It allows us to temporarily modify environment variables within the scope of our tests, ensuring that our code behaves correctly under different platform configurations. By using Climate Control, we can simulate different platform environments without affecting the actual system-wide environment variables.&lt;/p&gt;

&lt;p&gt;To make it work, we will add metadata named &lt;code&gt;current_platform&lt;/code&gt; with the desired platform value. This metadata will be associated with our test example or test suite, depending on how we want to configure it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/lib/settings/platform/sample_spec.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Sample'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'#admin'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:platform_admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;klass&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;platform_admin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Class&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Platform&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mixin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:admin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns correct platform specific values'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;current_platform: &lt;/span&gt;&lt;span class="s1"&gt;'third_platform'&lt;/span&gt;  &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enabled?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_page_1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_page_1?&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;platform_admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;available_pages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&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;Our &lt;code&gt;current_platform&lt;/code&gt; RSpec metadata logic looks like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&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;:example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_platform&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="no"&gt;ClimateControl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt; &lt;span class="no"&gt;CURRENT_PLATFORM&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;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:current_platform&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;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;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;Also, don't forget to include it in your &lt;code&gt;spec/rails_helper.rb&lt;/code&gt; file.&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;# spec/rails_helper.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'support/current_platform'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it!&lt;/p&gt;

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

&lt;p&gt;In this article, we have explored the concept of Platform Settings in a Rails application and delved into the reasons behind their necessity. We have identified the specific problems they aim to solve and discussed four different available options to implement them, weighing their respective advantages and disadvantages. Based on our analysis, we have chosen the most suitable approach and implemented it in detail, addressing the issues encountered along the way.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>patterns</category>
    </item>
    <item>
      <title>Say Goodbye to Messy Constants: A New Approach to Moving Constants Away from Your Model!</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Sun, 30 Apr 2023 09:40:38 +0000</pubDate>
      <link>https://forem.com/vladhilko/say-goodbye-to-messy-constants-a-new-approach-to-moving-constants-away-from-your-model-58i1</link>
      <guid>https://forem.com/vladhilko/say-goodbye-to-messy-constants-a-new-approach-to-moving-constants-away-from-your-model-58i1</guid>
      <description>&lt;h2&gt;
  
  
  Overview:
&lt;/h2&gt;

&lt;p&gt;In this article, we will discuss the usage of constants in a Rails application. We'll take a deep dive into the existing approaches of defining constants in Rails, including the use of configuration files, initializers, and model files. We'll also examine the pros and cons of each approach.&lt;/p&gt;

&lt;p&gt;We'll then provide a better approach to defining constants in Rails. We'll discuss the benefits, including how they can help prevent errors and make it easier to change values throughout the application. We'll also consider best practices for using constants in Rails, such as using meaningful names and limiting the scope of constants.&lt;/p&gt;

&lt;p&gt;Finally, we'll implement a production-ready solution that solves most of the problems associated with the standard approach. By the end of the article, we will have a clear understanding of how to use constants in our Rails application and we'will be able to implement a more efficient and effective approach to defining constants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, a constant is a special kind of value that never changes while a program is running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What standard options do we have for defining constants in Rails?&lt;/strong&gt; 🤷🏻‍♂️&lt;/p&gt;

&lt;p&gt;When it comes to defining constants in Rails, we have a few standard options to choose from. Let's take a closer look at three of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Option 1.&lt;/strong&gt; Don't define constants at all and just use simple primitive data types such as strings, numbers, or symbols.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option 2.&lt;/strong&gt; Define constants within the classes or modules where they are used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option 3.&lt;/strong&gt; Define global constants either in initializers or at the Rails configuration level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 1. Don't define constants at all and just use simple primitive data types such as strings, numbers, or symbols.
&lt;/h3&gt;

&lt;p&gt;As an example, let's consider the following scenario:&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;update&lt;/span&gt;
  &lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&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;type: &lt;/span&gt;&lt;span class="s1"&gt;'cat'&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;animal&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 this example, we're just using the string 'cat' instead of defining a new constant. The main advantage of this approach is its simplicity, but there are many potential problems that may appear in the future. Let's discuss them more closely:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typos and errors.&lt;/strong&gt; When using strings instead of constants, it's easy to make typos or use slightly different string literals, which can lead to errors that are difficult to debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance and refactoring.&lt;/strong&gt; If you use the same string literal in multiple places throughout your code, it can be difficult to change or update the value later on. This can make your code harder to maintain and refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistency.&lt;/strong&gt; If different parts of your code use different string literals to represent the same value, it can lead to inconsistencies and make your code harder to understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of systematization.&lt;/strong&gt; It's not clear to which domain or model this string belongs, nor what other options are available. As a result, everything is scattered throughout the app, leading to disorganization and difficulties in maintenance and modification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2. Define constants within the classes or modules where they are used.
&lt;/h3&gt;

&lt;p&gt;The second option suggests defining a new constant somewhere on the business logic level, for example, in the model, service, or module. Let's take a look at this code:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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="no"&gt;TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pig'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="no"&gt;COLORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've defined a constant array on the model level. It solves some problems that were introduced in &lt;code&gt;Option 1&lt;/code&gt; such as &lt;strong&gt;Typos and errors&lt;/strong&gt;, &lt;strong&gt;Inconsistency&lt;/strong&gt;, and &lt;strong&gt;Lack of systematization&lt;/strong&gt;. However, we still have the following problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problems:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interface is not user-friendly. You cannot access a &lt;code&gt;green&lt;/code&gt; color, for example, by simply doing &lt;code&gt;Animal::COLORS.green&lt;/code&gt;. Therefore, you have to define a new constant (for example, &lt;code&gt;COLOR_GREEN = 'green'&lt;/code&gt;, &lt;code&gt;COLOR_WHITE = 'white'&lt;/code&gt;) for each color or use a hash.&lt;/li&gt;
&lt;li&gt;Having a large number of constants in your model increases the model's complexity and reduces its readability, ultimately making it harder to maintain.&lt;/li&gt;
&lt;li&gt;You cannot define and use something that is not related to the business logic in the model. For instance, default system data such as &lt;code&gt;limit&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;, and &lt;code&gt;pagination&lt;/code&gt; cannot be defined in the model. This increases cognitive load and you have to always think about the best place to keep such data.&lt;/li&gt;
&lt;li&gt;When you define a constant within a class or module in your Rails model, it will only be accessible within that class or module. This can be problematic if you need to use the constant elsewhere in your code, as you may have to duplicate it or use an unattractive model prefix to access it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 3. Define global constants either in initializers or at the Rails configuration level.
&lt;/h3&gt;

&lt;p&gt;In this option, we will consider defining constants at the global level. There are two ways to do it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Define constants in the initializer file&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define constants in the configuration file&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Define constants in the initializer file
&lt;/h4&gt;

&lt;p&gt;We can define the constant in the initializer file, for example in &lt;code&gt;config/initializers/animal.rb&lt;/code&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;# config/initializers/animal.rb&lt;/span&gt;

&lt;span class="no"&gt;TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'cat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pig'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="no"&gt;COLORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This way, the constants will be loaded with the Rails application and can be accessed globally throughout the application. The main problems of this approach are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Non-user-friendly interface&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of namespaces&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Difficulty in maintenance and scalability&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Define constants in the configuration file
&lt;/h4&gt;

&lt;p&gt;To overcome the disadvantages mentioned above, we can try using the following approach: creating separate YAML files containing all of our data for each model. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/animal.yml&lt;/span&gt;

&lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dog&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pig&lt;/span&gt;
&lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;red&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;white&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we will load this data in the &lt;code&gt;config/application.rb&lt;/code&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;# config/application.rb&lt;/span&gt;

&lt;span class="c1"&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;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/config/animal.yml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that all data will be available by using the following interface:&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;Rails&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;animal&lt;/span&gt;

&lt;span class="c1"&gt;# {"types"=&amp;gt;["cat", "dog", "pig"], "colors"=&amp;gt;["green", "red", "white", "blue"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although it solved some of the problems, it still looks strange. Therefore, we are going to implement a new approach that will gather all the advantages from every approach we discussed here.&lt;/p&gt;




&lt;h2&gt;
  
  
  A new approach for defining constants
&lt;/h2&gt;

&lt;p&gt;In this section, we'll consider creating a custom approach for defining constants to solve the problems that were mentioned above. We will start by &lt;strong&gt;gathering requirements&lt;/strong&gt;, then we will define &lt;strong&gt;the interface&lt;/strong&gt;. After that, we will &lt;strong&gt;implement three approaches&lt;/strong&gt; to show the evaluation from the simplest to the production-ready approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Let's describe what we want to achieve in the end. Our custom constant should meet the following 5 requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The constant should be available globally.&lt;/li&gt;
&lt;li&gt;The constant should have a clear and straightforward interface.&lt;/li&gt;
&lt;li&gt;The constant should be easily extendable and maintainable.&lt;/li&gt;
&lt;li&gt;The constant should be frozen and not modifiable after initialization.&lt;/li&gt;
&lt;li&gt;The constant should be namespaced to avoid overwriting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interface
&lt;/h3&gt;

&lt;p&gt;What interface do we want for our custom implementation? Personally, I would like to have something that looks like &lt;code&gt;Rails.configuration.animal&lt;/code&gt; from &lt;code&gt;Option 3&lt;/code&gt; that we discussed earlier, but with 4 additional features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct access to any array element:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&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;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; cat&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;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dog&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; dog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ability to return all values from the defined array:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&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;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['cat', 'dog', 'pig']&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;configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['green', 'red', 'white', 'blue']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Using a name that is related to constant instead of &lt;code&gt;Rails.configuration&lt;/code&gt;:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; cat &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep constants namespaced for each separated model:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; cat &lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truck&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; truck&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting it all together, we get the following interface:&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;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; cat &lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; green&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truck&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; truck&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['green', 'red', 'white', 'blue']&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['sedan', 'minivan', 'truck']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;We'll look at three different options for addressing the issue, beginning with the most straightforward and ending with the most comprehensive solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1. One class/module that contains all the methods.
&lt;/h3&gt;

&lt;p&gt;In this option, we will initiate a new module called &lt;code&gt;Constants&lt;/code&gt; and add methods for each model that will return us &lt;code&gt;OpenStruct&lt;/code&gt; objects with all required data. So, we would have something like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Constants&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;
    &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;cat: &lt;/span&gt;&lt;span class="s1"&gt;'cat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;dog: &lt;/span&gt;&lt;span class="s1"&gt;'dog'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;pig: &lt;/span&gt;&lt;span class="s1"&gt;'pig'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;green: &lt;/span&gt;&lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;red: &lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;white: &lt;/span&gt;&lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;blue: &lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="no"&gt;OpenStruct&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;types: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&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;values: &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;colors: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&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;values: &lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;
    &lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;sedan: &lt;/span&gt;&lt;span class="s1"&gt;'sedan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;minivan: &lt;/span&gt;&lt;span class="s1"&gt;'minivan'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;truck: &lt;/span&gt;&lt;span class="s1"&gt;'truck'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="no"&gt;OpenStruct&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;types: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&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;values: &lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;freeze&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;And our interface would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["cat", "dog", "pig"]&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["sedan", "minivan", "truck"]&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truck&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "truck"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface looks good, but we still have many open issues that don't align with our requirements, like these ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is hard to maintain and extend because everything is in one place.&lt;/li&gt;
&lt;li&gt;There is a high risk of breaking something because we have to create every method from scratch.&lt;/li&gt;
&lt;li&gt;There is a high risk of invalid methods because no error is raised if a non-existent constant value is requested.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not_exist&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why we need to build a more scalable and reliable solution. Let's consider &lt;strong&gt;Option 2&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option 2. Storing constants in YAML files
&lt;/h2&gt;

&lt;p&gt;In this option, we will improve scalability and move all our constant data to YAML files, as we did in the example with Rails configuration above. Here is our plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1.&lt;/strong&gt; Create a new YAML file for each model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2.&lt;/strong&gt; Load all these YAML files and combine them into one big hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3.&lt;/strong&gt; Transform this hash into a nested OpenStruct object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4.&lt;/strong&gt; Assign this new OpenStruct object to a single constant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1.&lt;/strong&gt; Create a new YAML file for each model.
&lt;/h3&gt;

&lt;p&gt;In this example, we will create 2 files: &lt;code&gt;animal.yml&lt;/code&gt; and &lt;code&gt;car.yml&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;animal.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/constants/animal.yml&lt;/span&gt;

&lt;span class="na"&gt;animal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dog&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pig&lt;/span&gt;
  &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;red&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;white&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt;
  &lt;span class="na"&gt;ages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;one_week&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 week&lt;/span&gt;
    &lt;span class="na"&gt;one_month&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 month&lt;/span&gt;
    &lt;span class="na"&gt;one_year&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 year&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;car.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/constants/car.yml&lt;/span&gt;

&lt;span class="na"&gt;car&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sedan&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;minivan&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;truck&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2.&lt;/strong&gt;  Load all these YAML files and combine them into one big hash.
&lt;/h3&gt;

&lt;p&gt;To achieve this, we can do the following:&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;constant_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'config/constants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.yml'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;reduce&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="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; &lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="s2"&gt;"animal"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="s2"&gt;"types"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"cat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"dog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pig"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"colors"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s2"&gt;"ages"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"one_week"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"1 week"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"one_month"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"1 month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="s2"&gt;"one_year"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"1 year"&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="s2"&gt;"car"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="s2"&gt;"types"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sedan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"minivan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"truck"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3.&lt;/strong&gt;  Transform this hash into a nested OpenStruct object.
&lt;/h3&gt;

&lt;p&gt;To transform the hash into a nested OpenStruct object, we can define the following method in the Hash 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;Hash&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_open_struct&lt;/span&gt;
    &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt; &lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;object_class: &lt;/span&gt;&lt;span class="no"&gt;OpenStruct&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;And then use this method on the hash:&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;constant_open_struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constant_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_open_struct&lt;/span&gt;

&lt;span class="n"&gt;constant_open_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["cat", "dog", "pig"]&lt;/span&gt;
&lt;span class="n"&gt;constant_open_struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one_week&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "1 week"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4.&lt;/strong&gt;  Assign this new OpenStruct object to a single constant.
&lt;/h3&gt;

&lt;p&gt;Finally, we need to create a new constant and assign this new OpenStruct to it in the Rails initializer:&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;# config/initializers/constants.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Constants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;constant_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_open_struct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Now we have the following interface:&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;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one_month&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "1 month"&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["cat", "dog", "pig"]&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["sedan", "minivan", "truck"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution is much more flexible now, but there are still many open problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constant values are not frozen.&lt;/li&gt;
&lt;li&gt;Without the hash, it is not possible to access elements from an array. For example, the following constant &lt;code&gt;Constants.animal.types.cat&lt;/code&gt; doesn't work.&lt;/li&gt;
&lt;li&gt;The OpenStruct performance is not good.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why we need to consider &lt;strong&gt;Option 3&lt;/strong&gt; to fix these problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option 3. Production Ready Solution
&lt;/h2&gt;

&lt;p&gt;In the final solution, we aim to preserve all the advantages and address the drawbacks of the previous solution. This solution will be nearly identical to the previous one, except for one difference: we will create a new custom class instead of using OpenStruct. To implement this, we need to follow these four steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1.&lt;/strong&gt; Create a new YAML file for each model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2.&lt;/strong&gt; Load all these YAML files and combine them into one big hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3.&lt;/strong&gt; Create a new custom class to store our core logic for the new constant object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4.&lt;/strong&gt; Transform the hash from &lt;strong&gt;Step 2&lt;/strong&gt; into an instance of the class created in &lt;strong&gt;Step 3&lt;/strong&gt;. Then, assign this object to a new constant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1.&lt;/strong&gt; Create a new YAML file for each model.
&lt;/h3&gt;

&lt;p&gt;As a first step, we'll add YAML files just like we did in the previous option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;animal.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/constants/animal.yml&lt;/span&gt;

&lt;span class="na"&gt;animal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cat&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dog&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pig&lt;/span&gt;
  &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;green&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;red&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;white&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blue&lt;/span&gt;
  &lt;span class="na"&gt;ages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;one_week&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 week&lt;/span&gt;
    &lt;span class="na"&gt;one_month&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 month&lt;/span&gt;
    &lt;span class="na"&gt;one_year&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 year&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;car.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/constants/car.yml&lt;/span&gt;

&lt;span class="na"&gt;car&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sedan&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;minivan&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;truck&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2.&lt;/strong&gt; Load all these YAML files and combine them into one big hash.
&lt;/h3&gt;

&lt;p&gt;At the second step, we will load all YAML files and combine them into one big hash, as we did in the previous option. Additionally, we will wrap it in a separate class for our convenience:&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;# lib/constant/load.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Constant&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Load&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;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&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="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&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;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.yml'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;reduce&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="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

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

    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:path&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;h3&gt;
  
  
  &lt;strong&gt;Step 3.&lt;/strong&gt; Create a new custom class to store our core logic for the new constant object.
&lt;/h3&gt;

&lt;p&gt;Let's take a look at the implementation, here's how our new class will look like. Don't worry if it seems complicated; we'll go over how this class works later:&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;# lib/constant/model.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Constant&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Model&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;constant_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="vi"&gt;@constant_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constant_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deep_symbolize_keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&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;deep_transform&lt;/span&gt;
      &lt;span class="n"&gt;tap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;constant_hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;define_singleton_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;initialize_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# provides missing hash methods ( for example '.values' etc. )&lt;/span&gt;
    &lt;span class="n"&gt;delegate_missing_to&lt;/span&gt; &lt;span class="ss"&gt;:constant_hash&lt;/span&gt;

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

    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:constant_hash&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Hash&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deep_transform&lt;/span&gt;
      &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="no"&gt;Array&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:itself&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;deep_transform&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&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;The idea behind this class is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We send a hash from &lt;strong&gt;Step 2&lt;/strong&gt; as an argument.&lt;/li&gt;
&lt;li&gt;We iterate over each key of this hash and define a new method for each key, for example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# hash = { animal: { limit: 10 } }&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In this method, there are three options that we can return depending on the value of the key:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1. Primitive type.&lt;/strong&gt; If the hash value for this key is a primitive type, for example, a string/symbol/integer, then we return this value.&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;# hash = { limit: 10 }&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;limit&lt;/span&gt;
  &lt;span class="mi"&gt;10&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;2. Hash.&lt;/strong&gt; If the value for this key is a Hash, then we return an instance of our new class with this value as an argument:&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;# hash = { animal: { limit: 10 } }&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal&lt;/span&gt;
  &lt;span class="no"&gt;Constant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&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;limit: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;deep_transform&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;3. Array.&lt;/strong&gt; If the value for this key is an Array, we transform this array into a hash and return an instance of our new class with this value as an argument:&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;# hash_with_array = { types: ['cat', 'dog'] }&lt;/span&gt;
&lt;span class="c1"&gt;# hash_with_hash = { types: { cat: 'cat', dog: 'dog' } } &lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;types&lt;/span&gt;
  &lt;span class="no"&gt;Constant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&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;cat: &lt;/span&gt;&lt;span class="s1"&gt;'cat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dog: &lt;/span&gt;&lt;span class="s1"&gt;'dog'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;deep_transform&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And don't forget to freeze everything. That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4.&lt;/strong&gt; Transform the hash from &lt;strong&gt;Step 2&lt;/strong&gt; into an instance of the class created in &lt;strong&gt;Step 3&lt;/strong&gt;. Then, assign this object to a new constant.
&lt;/h3&gt;

&lt;p&gt;Our final step is to bind &lt;strong&gt;Step 2&lt;/strong&gt; and &lt;strong&gt;Step 3&lt;/strong&gt;, and save the result to a new constant. We will move this initialization into a separate 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="c1"&gt;# lib/constant/initialize.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'load'&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s1"&gt;'model'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Constant&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Initialize&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;path&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;constant_name&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="vi"&gt;@path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
      &lt;span class="vi"&gt;@constant_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constant_name&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="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;constant_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;load_hash_from_yml&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deep_transform&lt;/span&gt;&lt;span class="p"&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="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:constant_name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_hash_from_yml&lt;/span&gt;
      &lt;span class="no"&gt;Constant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we will run this class in the initializer:&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;# config/initializers/constants.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'constant/initialize'&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Constant&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Initialize&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;path: &lt;/span&gt;&lt;span class="s1"&gt;'config/constants'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;constant_name: &lt;/span&gt;&lt;span class="s1"&gt;'Constants'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, now we have our desired interface:&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;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cat&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; cat &lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;green&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; green&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; 10&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["1 week", "1 month", "1 year"]&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['green', 'red', 'white', 'blue']&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truck&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; truck&lt;/span&gt;
&lt;span class="no"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ['sedan', 'minivan', 'truck']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here's the main benefit that we receive using this new approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clear Interface.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintenance and refactoring.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DRY code.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data validity.&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You can prevent typos and invalid data from being saved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Systematization.&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;It's easy to understand which domain/model a value belongs to and what other options are available.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Immutability.&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Immutable values prevent accidental changes and ensure that the value remains consistent throughout your application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency.&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;You can ensure that the same value is represented consistently throughout your code.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>patters</category>
      <category>constant</category>
    </item>
    <item>
      <title>How to implement Pub/Sub pattern in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Mon, 10 Apr 2023 18:22:43 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-implement-pubsub-pattern-in-ruby-on-rails-1l5p</link>
      <guid>https://forem.com/vladhilko/how-to-implement-pubsub-pattern-in-ruby-on-rails-1l5p</guid>
      <description>&lt;h2&gt;
  
  
  Overview:
&lt;/h2&gt;

&lt;p&gt;In this article we'll provide a comprehensive guide to understanding and implementing the Pub/Sub pattern. We will explore the evolution of this pattern from a primitive implementation to three product-ready solutions: &lt;code&gt;ActiveSupport::Notifications&lt;/code&gt;, &lt;code&gt;Wisper&lt;/code&gt;, and &lt;code&gt;Dry-Events&lt;/code&gt;. We will begin by discussing the core concept of the Pub/Sub pattern. Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will provide a step-by-step guide on how to implement the Pub/Sub pattern using the basic Ruby language constructs.&lt;/li&gt;
&lt;li&gt;We will introduce the &lt;code&gt;ActiveSupport::Notifications&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;We will delve into the &lt;code&gt;Wisper&lt;/code&gt; gem.&lt;/li&gt;
&lt;li&gt;Finally, we will examine the &lt;code&gt;Dry-Events&lt;/code&gt; gem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this article, we will have a solid understanding of the Pub/Sub pattern and the different options available for implementing it in their Ruby on Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, the Pub/Sub (Publish/Subscribe) pattern is a way of decoupling components in a system by providing an alternative method of communication between them. Rather than sending messages directly to specific components, a publisher broadcasts messages to any subscribers that are interested in receiving them. In other words, the Pub/Sub pattern enables communication between different components of a system without these components having any knowledge of each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do we need it and what problems can this pattern solve?&lt;/strong&gt; 🤷🏻‍♂️&lt;/p&gt;

&lt;p&gt;Let's take a look at the following piece of code from the rails controller.&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;create&lt;/span&gt;
  &lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# send_email&lt;/span&gt;
  &lt;span class="c1"&gt;# send_mobile_notification&lt;/span&gt;
  &lt;span class="c1"&gt;# save_logs&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;animal&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 are we doing here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We want to create an Animal record and show this record in the response&lt;/li&gt;
&lt;li&gt;PLUS send an email about it&lt;/li&gt;
&lt;li&gt;PLUS send a mobile notification about it&lt;/li&gt;
&lt;li&gt;PLUS save this action to the logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do we have any problems with this approach?&lt;/strong&gt; 🤔&lt;/p&gt;

&lt;p&gt;Yes, we have. This code can lead to several problems, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced maintainability&lt;/strong&gt;: With multiple unrelated tasks in the same controller action, it can be difficult to maintain or modify the code in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced testability&lt;/strong&gt;: Testing the create action becomes more complicated due to the additional responsibilities it has. It also makes it more challenging to test these other responsibilities in isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced scalability&lt;/strong&gt;: If additional functionality needs to be added, it would likely be added to the create action, which can make the controller even more complicated and challenging to maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced readability&lt;/strong&gt;: Mixing unrelated functionality in the same controller action can make it difficult to understand the overall purpose of the action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can we solve this problem? 🤔
&lt;/h2&gt;

&lt;p&gt;Some developers solve this problem by using callbacks on the model to handle it. So, they might use something like this::&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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_create&lt;/span&gt; &lt;span class="ss"&gt;:send_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:send_mobile_notification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:save_logs&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;send_email&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Email will be sent'&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;send_mobile_notification&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Mobile Notification will be sent'&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;save_logs&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Logs will be saved'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But callbacks can potentially lead to significant issues, so it is better to avoid them.&lt;/p&gt;

&lt;p&gt;Potentially, we could create &lt;a href="https://dev.to/vladhilko/how-to-implement-service-object-pattern-in-ruby-on-rails-2mh8"&gt;a separate service object&lt;/a&gt; and place all this logic inside it. For 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;# app/units/create_animal_service.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateAnimalService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# do something&lt;/span&gt;
    &lt;span class="n"&gt;send_email&lt;/span&gt;
    &lt;span class="n"&gt;send_mobile_notification&lt;/span&gt;
    &lt;span class="n"&gt;save_logs&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;send_email&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Email will be sent'&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;send_mobile_notification&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Mobile Notification will be sent'&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;save_logs&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'Logs will be saved'&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;While this approach is an improvement over using model callbacks, there is still a significant amount of cohesion between the core business logic and non-critical secondary logic. As a result, developers must understand the secondary logic, which is closely tied to the core logic, leading to increased cognitive load and a shift in focus from the primary part to technical details.&lt;/p&gt;

&lt;p&gt;To address these issues, we can use the Pub/Sub pattern to separate the core business logic from non-critical secondary logic. Let's discuss this further:&lt;/p&gt;

&lt;h2&gt;
  
  
  How pub/sub actually works?
&lt;/h2&gt;

&lt;p&gt;There are three key elements that must be implemented to make Pub/Sub work for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send/Publish/Broadcast an event.&lt;/li&gt;
&lt;li&gt;Define the logic that can potentially react to this event.&lt;/li&gt;
&lt;li&gt;Bind the event with the logic ("subscribe to the event").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we would like to have something like this as our new interface:&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;create&lt;/span&gt;
  &lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;send_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;animal&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 interface would allow us to concentrate on the core business logic and delegate all 'secondary' logic to somewhere else.&lt;/p&gt;

&lt;p&gt;With the key elements above in mind, let's try to build a primitive Pub/Sub logic using plain Ruby to understand the concept behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Primitive Ruby implementation
&lt;/h2&gt;

&lt;p&gt;In this implementation, we'll use the same three main elements: 'Publishing an event', 'Defining logic' and 'Binding the event to the logic'.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing an Event
&lt;/h3&gt;

&lt;p&gt;For example, let's consider an event named &lt;code&gt;animal_created&lt;/code&gt;, which is represented as a string. Whenever this event is sent, we simply add this string to an array&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;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'animal_created'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defining logic to react to the event (subcriptions)
&lt;/h3&gt;

&lt;p&gt;To react to the event, we need to define some actions that can potentially happen when the event occurs. These actions are called &lt;code&gt;subscriptions&lt;/code&gt; or &lt;code&gt;listeners&lt;/code&gt;. We can use a simple hash with two keys: &lt;code&gt;event_name&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt;. &lt;code&gt;event_name&lt;/code&gt; is a string that identifies the event, and &lt;code&gt;action&lt;/code&gt; is a proc that is executed when the event is received.&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;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;event_name: &lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'hello animal!'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;event_name: &lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'hello animal 2!'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Binding events and subscriptions
&lt;/h3&gt;

&lt;p&gt;We have defined two main elements, but there is a problem - events and subscriptions are not connected and do not know about each other. To address this, we need to introduce some logic that will iterate over new events and match them with the appropriate action defined in the subscriptions.&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;events&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;event_name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:event_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;event_name&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, that is the basic idea. Let's try to improve our implementation by encapsulating some logic in a class.&lt;/p&gt;




&lt;h2&gt;
  
  
  Primitive Ruby implementation using class
&lt;/h2&gt;

&lt;p&gt;We will create a class called &lt;code&gt;Notification&lt;/code&gt;, which will have 3 methods: &lt;code&gt;publish&lt;/code&gt;, &lt;code&gt;subscribe&lt;/code&gt; and &lt;code&gt;initialize&lt;/code&gt;. In this implementation, we will immediately react to the published event as soon as it is sent.&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;Notification&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="c1"&gt;# We are initializing a Hash with empty arrays as default values for any new key&lt;/span&gt;
    &lt;span class="vi"&gt;@notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Hash&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="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;action&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;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&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="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:notifications&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a usage 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;# Initialize the class&lt;/span&gt;
&lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="c1"&gt;# Subscribe to the event "animal_created" with two actions.&lt;/span&gt;
&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'hello animal!'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'hello animal 2!'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Publish the 'animal_created' event, which will trigger both actions.&lt;/span&gt;
&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out three key elements have also been used in this implementation.&lt;/p&gt;




&lt;p&gt;This is a very simple example of how pub/sub can work. However, there are many ready-made solutions that we can use in our application. Let's consider three of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ActiveSupport::Notification&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Wisper&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dry-events&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They're pretty much the same and have very similar interfaces. Let's look at each of these options one by one to better understand the concept.&lt;/p&gt;




&lt;h2&gt;
  
  
  ActiveSupport::Notification
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html"&gt;ActiveSupport::Notification &lt;/a&gt; has the same three key elements that we discussed earlier&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send/Publish/Broadcast an event.&lt;/li&gt;
&lt;li&gt;Define the logic that can potentially react to this event.&lt;/li&gt;
&lt;li&gt;Bind the event with the logic ("subscribe to the event").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's consider three ways of implementing the pub/sub pattern using &lt;code&gt;ActiveSupport::Notification&lt;/code&gt;, starting from the simplest and progressing to the most complex, in order to better understand the concept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ActiveSupport::Notification&lt;/code&gt; with blocks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ActiveSupport::Notification&lt;/code&gt; with classes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ActiveSupport::Notification&lt;/code&gt; in a Rails application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start with the most basic one &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ActiveSupport::Notification&lt;/code&gt; with blocks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Define the logic&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&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;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"New animal created 1"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&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;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"New animal created 2"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Send the event&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; New animal created 1&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; New animal created 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, here everything looks obvious. Basically, we have the same interface as we described in the primitive Ruby implementation. Let's go further.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ActiveSupport::Notification&lt;/code&gt; with classes
&lt;/h3&gt;

&lt;p&gt;Instead of a block, we can also send a class that has a &lt;code&gt;call&lt;/code&gt; method. This can be useful when your logic becomes too complex, and using classes can greatly simplify testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Define the logic&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Subscription1&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;"New animal created 1"&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;Subscription2&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="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;"New animal created 2"&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;# Bind the event with the logic&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Subscription1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Subscription2&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="c1"&gt;# Send the event&lt;/span&gt;
&lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; New animal created 1&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; New animal created 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how &lt;code&gt;ActiveSupport::Notification&lt;/code&gt; basically works. Let's try to add it to a real Rails application.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ActiveSupport::Notification&lt;/code&gt; in a Rails application
&lt;/h3&gt;

&lt;p&gt;For example, we have a service object that performs some actions and sends the &lt;code&gt;'animal_created'&lt;/code&gt; event. &lt;/p&gt;

&lt;h4&gt;
  
  
  Publishing an Event
&lt;/h4&gt;



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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Service&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# perform some actions&lt;/span&gt;
    &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &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;h4&gt;
  
  
  Defining logic to react to the event
&lt;/h4&gt;

&lt;p&gt;Additionally, we need to create subscriptions to react to these events. We want to send an email, send a mobile notification, and log this action somewhere. To accomplish this, we'll create three subscription classes, each of which will handle one of these tasks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EmailSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSubscription&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Subscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Email will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;ul&gt;
&lt;li&gt;
&lt;code&gt;MobileNotificationSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileNotificationSubscription&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Subscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Mobile Notification will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;ul&gt;
&lt;li&gt;
&lt;code&gt;LoggerSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerSubscription&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Subscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Logs will be saved &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;Every subscription class should include the desired reaction to the event and a method call to actually run this reaction. That's why we've created a parent class &lt;code&gt;Subscription&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;Subscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Subscription&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;event_name&lt;/span&gt;&lt;span class="p"&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;_&lt;/span&gt;&lt;span class="p"&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;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&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;h4&gt;
  
  
  Binding events and logic
&lt;/h4&gt;

&lt;p&gt;We've created an event that can be sent and classes with the appropriate reaction to the event. However, these classes aren't currently connected to the event, meaning they won't react to it. To establish the connection, we'll use the following logic after Rails initialization.&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;# config/initializers/subscriptions.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;EmailSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="no"&gt;LoggerSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="no"&gt;MobileNotificationSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# We iterate over each subscription class and bind its logic with the event name, similar to what we did in the example with blocks.&lt;/span&gt;
  &lt;span class="n"&gt;subscriptions&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;subscription_class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;events&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;event&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subscription_class&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="nf"&gt;constantize&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="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;So now we can call &lt;code&gt;Service.call&lt;/code&gt; and this code will send the 'animal_created' event, triggering all subscriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Email will be sent {:payload=&amp;gt;{}}
Logs will be saved {:payload=&amp;gt;{}}
Mobile Notification will be sent {:payload=&amp;gt;{}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Adding a new event
&lt;/h4&gt;

&lt;p&gt;To add a new event, you need to update the subscription classes with the desired logic and then bind them to the new event. This ensures that the subscription logic will be executed whenever the event is triggered.&lt;/p&gt;

&lt;p&gt;For example, if you would like to add the &lt;code&gt;car_created&lt;/code&gt; event to the EmailSubscription, then you should do the following:&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;# app/subscriptions/email_subscription.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSubscription&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Subscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Email will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;def&lt;/span&gt; &lt;span class="nf"&gt;car_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Car email will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;It's important not to forget to subscribe to this event in the initializer after adding the logic to the &lt;code&gt;EmailSubscription&lt;/code&gt; class for the &lt;code&gt;car_created&lt;/code&gt; event.&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;# config/initializers/subscriptions.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;EmailSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'car_created'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="no"&gt;LoggerSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="no"&gt;MobileNotificationSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we call &lt;code&gt;car_created&lt;/code&gt;, the subscribed logic in &lt;code&gt;EmailSubscription&lt;/code&gt; will be triggered as expected.&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;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'car_created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="c1"&gt;# =&amp;gt; Car email will be sent {:payload=&amp;gt;{}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Wisper gem
&lt;/h2&gt;

&lt;p&gt;The second option is to use &lt;a href="https://github.com/krisleech/wisper"&gt;&lt;code&gt;Wisper&lt;/code&gt;&lt;/a&gt; gem.&lt;/p&gt;

&lt;p&gt;First of all, we need to add the &lt;code&gt;wisper&lt;/code&gt; gem to the &lt;code&gt;Gemfile&lt;/code&gt; and then run &lt;code&gt;bundle install&lt;/code&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'wisper'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gem has the same three key elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send/Publish/Broadcast an event.&lt;/li&gt;
&lt;li&gt;Define the logic that can potentially react to this event.&lt;/li&gt;
&lt;li&gt;Bind the event with the logic ("subscribe to the event").&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Send an event.
&lt;/h3&gt;

&lt;p&gt;To send an event we need to do the following:&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;# app/units/create_animal_service.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateAnimalService&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Wisper&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publisher&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# do something&lt;/span&gt;
    &lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:animal_created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &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="no"&gt;CreateAnimalService&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;h3&gt;
  
  
  Define the logic
&lt;/h3&gt;

&lt;p&gt;To add logic for our events, we need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EmailSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Email will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;ul&gt;
&lt;li&gt;
&lt;code&gt;LoggerSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Logs will be saved &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;ul&gt;
&lt;li&gt;
&lt;code&gt;MobileNotificationSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileNotificationSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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;"Mobile Notification will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;payload&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;h3&gt;
  
  
  Bind the event with the logic
&lt;/h3&gt;

&lt;p&gt;To bind the event and subscription, we need to do the following&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;# config/initializers/subscriptions.rb&lt;/span&gt;

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Wisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EmailSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Wisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LoggerSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Wisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MobileNotificationSubscription&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="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;As you can see, the interface is quite similar to what we had in &lt;code&gt;ActiveSupport::Notifications&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dry-event
&lt;/h2&gt;

&lt;p&gt;Our third option is to create a Pub/Sub system using the &lt;a href="https://dry-rb.org/gems/"&gt;&lt;code&gt;dry-rb&lt;/code&gt;&lt;/a&gt; gems. To be more precise, we are going to use the &lt;a href="https://github.com/dry-rb/dry-events"&gt;&lt;code&gt;dry-events&lt;/code&gt;&lt;/a&gt; gem. &lt;/p&gt;

&lt;p&gt;To get started, we need to add the &lt;code&gt;dry-events&lt;/code&gt; gem to our &lt;code&gt;Gemfile&lt;/code&gt; and run &lt;code&gt;bundle install&lt;/code&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;gem&lt;/span&gt; &lt;span class="s1"&gt;'dry-events'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gem has the same 3 key elements plus an additional one - we need to register an event. Let's take a look at the example below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Register an event
&lt;/h3&gt;



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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dry/events/publisher'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Events&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Dry&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Events&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Publisher&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:my_publisher&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;register_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal.created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We included &lt;code&gt;Singleton&lt;/code&gt; here because &lt;code&gt;dry-event&lt;/code&gt; requires us to work with an instance, but we don't want to create a new instance every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send an event
&lt;/h3&gt;



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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateAnimalService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# do something&lt;/span&gt;
    &lt;span class="no"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal.created'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;payload: &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;h3&gt;
  
  
  Defining logic to react to the event (Subscriptions)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EmailSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;"Email will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LoggerSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;"Logs will be saved &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MobileNotificationSubscription&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MobileNotificationSubscription&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_animal_created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;"Mobile Notification will be sent &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;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;
  
  
  Bind the event with the logic
&lt;/h2&gt;



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

&lt;span class="c1"&gt;# frozen_string_literal: true&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;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_initialize&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;

  &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EmailSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;LoggerSubscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MobileNotificationSubscription&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="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;






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

&lt;p&gt;&lt;strong&gt;The final solution may look like this or be placed under the Service object which sends an event inside:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="n"&gt;animal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal_params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'animal_created'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="n"&gt;animal&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 conclusion, Pub/Sub pattern is a useful design pattern in Ruby on Rails applications for managing communication between different parts of the system. The advantages of using Pub/Sub are numerous, including the most important ones:&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Decoupling&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One of the main advantages of the pub/sub pattern is that it allows publishers and subscribers to be decoupled from each other. Publishers do not need to know about the subscribers, and subscribers do not need to know about the publishers. This makes it easy to add or remove components from the system without affecting the other components.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pub/sub pattern is highly scalable, since it allows multiple subscribers to receive the same message at the same time. This makes it easy to distribute messages to a large number of subscribers, without overloading the publishers or the subscribers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pub/sub pattern provides a flexible way to implement communication between different components of an application, as well as between different applications. Since publishers and subscribers do not need to know about each other, it is easy to add new features or change existing ones without having to modify existing code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pub/sub pattern is asynchronous, which means that publishers and subscribers do not need to be active at the same time. Publishers can publish messages at any time, and subscribers can consume messages whenever they are ready. This makes it easy to implement real-time notifications and messaging systems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvatages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Implementing the pub/sub pattern can be complex, especially when dealing with a large number of publishers and subscribers. This pattern can add an additional layer of complexity to the system, which can make it harder to debug and maintain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduction of application flow visibility&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another potential disadvantage of using the Pub/Sub pattern is that it can result in a reduction of application flow visibility. This means that the overall flow of the system may be harder to understand, as the primary action and its secondary effects may be implemented in different parts of the codebase, making it difficult for future readers to comprehend the system's operation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>patterns</category>
      <category>pubsub</category>
    </item>
    <item>
      <title>How to implement Authorizer pattern in Ruby on Rails?</title>
      <dc:creator>Vlad Hilko</dc:creator>
      <pubDate>Thu, 29 Dec 2022 22:22:34 +0000</pubDate>
      <link>https://forem.com/vladhilko/how-to-implement-authorizer-pattern-in-ruby-on-rails-2non</link>
      <guid>https://forem.com/vladhilko/how-to-implement-authorizer-pattern-in-ruby-on-rails-2non</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;In this article, we will explore the &lt;code&gt;Authorizer&lt;/code&gt; pattern in Ruby on Rails applications. We will provide a definition of the &lt;code&gt;Authorizer&lt;/code&gt; pattern and discuss its use cases and examples. We will also delve into the need for this pattern and how it helps to solve certain problems in Rails applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Definition
&lt;/h2&gt;

&lt;p&gt;In simple terms, the &lt;strong&gt;Authorizer&lt;/strong&gt; pattern allows us to define and encapsulate complex business rule in our Rails applications. If the requirement specified by this rule is not met, an error will be raised. You can think of an &lt;code&gt;Authorizer&lt;/code&gt; as a custom &lt;code&gt;ActiveRecord Validator&lt;/code&gt;, but instead of validating input data, it is used to enforce business requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do we need it and what problems can this pattern solve? 🤷🏻‍♂️&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at the following 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;def&lt;/span&gt; &lt;span class="nf"&gt;create&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;first&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'User is not active'&lt;/span&gt; &lt;span class="k"&gt;if&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;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&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;approved_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;

  &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Aricle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s1"&gt;'body'&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;article&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 the following example, we want to ensure that certain requirements are fully met before calling the business logic. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do we have any problems with this approach?&lt;/strong&gt; 🤔&lt;/p&gt;

&lt;p&gt;Yes, we have.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is not possible to test separately from the controller.&lt;/li&gt;
&lt;li&gt;It is not reusable.&lt;/li&gt;
&lt;li&gt;We violate the Single Responsibility Principle because the controller becomes responsible for business rules validation.&lt;/li&gt;
&lt;li&gt;The following code increases 'cognitive load' and requires more time to understand.&lt;/li&gt;
&lt;li&gt;The following code is not scalable and it is difficult to add more rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How can we solve these problems? 🤔
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We can move this logic to the model level and use &lt;code&gt;before_create&lt;/code&gt; callback.&lt;/li&gt;
&lt;li&gt;We can create &lt;code&gt;Athorizer&lt;/code&gt; class and move the logic there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a look at these options one by one. &lt;/p&gt;




&lt;h2&gt;
  
  
  Model with callback
&lt;/h2&gt;

&lt;p&gt;This is the easiest option, we just need to extend our model in the following way:&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;# app/models/article.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Aricle&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;before_save&lt;/span&gt; &lt;span class="ss"&gt;:check_user_status!&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_user_status!&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'User is not active'&lt;/span&gt; &lt;span class="k"&gt;if&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;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&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;approved_at&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;end&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;last&lt;/span&gt;
&lt;span class="no"&gt;Aricle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Are there any disadvantages to this approach?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There're 2 problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We violate the Single responsibility SOLID principle. The model becomes too &lt;code&gt;FAT&lt;/code&gt; and responsible for too many things. &lt;/li&gt;
&lt;li&gt;Using callbacks in Rails models can make the code more difficult to understand and maintain due to their tight coupling with the model and lack of transparency. Therefore, it is generally considered to be a bad pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So it would be nice to encapsulate this logic in a separate class. &lt;strong&gt;How can we do that?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Authorizer Pattern
&lt;/h2&gt;

&lt;p&gt;We're going to implement Athorizer Pattern via plain Ruby object because it's the clearest and the most straightforward solution.&lt;/p&gt;

&lt;p&gt;Let's take a look at the following 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;# app/authorizers/has_active_user_authorizer.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HasActiveUserAuthorizer&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;user&lt;/span&gt;&lt;span class="p"&gt;)&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;authorize!&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'User is not active'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;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="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;active?&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;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;approved_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;HasActiveUserAuthorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;authorize!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just encapsulated our rules inside a separate class, and that's it. This code adheres to the SOLID principles and is much easier to understand and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming convention
&lt;/h2&gt;

&lt;p&gt;There are two common ways to name authorizers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inside a namespace (for example, &lt;code&gt;Authorizers::HasActiveUser&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;With a postfix (for example, &lt;code&gt;HasActiveUserAuthorizer&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they have one rule in common - the authorizer must clearly reflect in its name the rule it introduces. Let's take a look at other possible names that are commonly used for the Authorizer Pattern to get a wider picture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CompanyHasNoEmployeesAuthorizer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if the company has at least one employee &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ArticleNotApprovedAuthorizer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if the article is approved&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authorizers::HasAtLeastOneComment&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if there are no comments&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authorizers::NotPopulated&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if the table is already populated with records&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UserHasActiveSubscriptionAuthorizer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if the user does not have an active subscription&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CustomerAgeApprovedAuthorizer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Raises an error if the customer's age is not approved&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;etc ...&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the Authorizer Pattern to Other Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Similar to Form Object&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Form objects and Authorizers both perform validation and raise errors before running business logic. So, what are the differences? The key differences are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form objects validate data from the user input&lt;/li&gt;
&lt;li&gt;Authorizers validate general business logic rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt;. To learn more about using Form object pattern in Ruby on Rails, you can check out this &lt;a href="https://dev.to/vladhilko/how-to-implement-form-object-pattern-in-ruby-on-rails-5gi3"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Similar to Policy Object&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Authorizers are also similar to Policy objects because both encapsulate business rules. The key differences are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorizers may contain only one business rule, but Policy objects can contain many.&lt;/li&gt;
&lt;li&gt;Authorizers are more strict and demand that a business rule be followed, whereas a Policy object will only ask if the rule is true or false. This difference is reflected in the use of &lt;code&gt;!&lt;/code&gt; by Authorizers and &lt;code&gt;?&lt;/code&gt; by Policy objects at the end of their methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt;. To learn more about using Policy object pattern in Ruby on Rails, you can check out this &lt;a href="https://dev.to/vladhilko/how-to-implement-policy-object-pattern-in-ruby-on-rails-54cb"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Similar to Custom Validators&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authorizers are similar to custom ActiveRecord validations in terms of their responsibility, but custom validators validate one data field, while Authorizers validate general rules for the whole model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Note:&lt;/strong&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't confuse the Authorizer pattern with authorization logic. Authorizers ask if the service is authorized to perform the action under the current conditions, while authorization logic happens at a higher level (usually on the controller level).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Bonus Chapter: Adding Authorizers to Service Objects
&lt;/h2&gt;

&lt;p&gt;In the previous chapter, we mentioned that Authorizers are similar to custom validators. Let's take a look at the example of custom validators:&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;# app/models/article.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&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="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="ss"&gt;:name_presence&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;name_presence&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Name is empty"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;name&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;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Or in a separate 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="c1"&gt;# app/models/article.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&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="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="n"&gt;validates_with&lt;/span&gt; &lt;span class="no"&gt;NamePresenceValidator&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/validators/name_presence.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NamePresenceValidator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Name is empty"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Essentially, we want to have a similar interface for our Authorizers within Service objects, something like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateArticleService&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="s1"&gt;'has_active_user_authorizer'&lt;/span&gt;
  &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="s1"&gt;'another_authorizer'&lt;/span&gt;
  &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="s1"&gt;'one_more_authorizer'&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How can we do this? Let's consider what we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service (to keep the &lt;code&gt;authorize&lt;/code&gt; method)&lt;/li&gt;
&lt;li&gt;A module with the &lt;code&gt;authorize&lt;/code&gt; method definition (to be able to include it in every service)&lt;/li&gt;
&lt;li&gt;Authorizer (to encapsulate complex business rule)&lt;/li&gt;
&lt;li&gt;A base Authorizer (to avoid repeating the same code for every new Authorizer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's add them one by one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: We will only provide code snippets without explanation to keep it brief.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service
&lt;/h2&gt;



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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateArticleService&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Authorizable&lt;/span&gt;

  &lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="s1"&gt;'has_active_user_authorizer'&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:params&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="ss"&gt;user: &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="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="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&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;authorize!&lt;/span&gt;
    &lt;span class="c1"&gt;# main logic&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;
  
  
  Authorizable module
&lt;/h2&gt;



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

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Authorizable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;class_attribute&lt;/span&gt; &lt;span class="ss"&gt;:authorizers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorizer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorizers&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;authorizer_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorize!&lt;/span&gt;
    &lt;span class="n"&gt;authorizers&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;authorizer&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;authorizer&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="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constantize&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="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;authorize!&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;h2&gt;
  
  
  Authorizer
&lt;/h2&gt;



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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HasActiveUserAuthorizer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseAuthorizer&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorize!&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'User is not active'&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;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;active?&lt;/span&gt;
    &lt;span class="n"&gt;service_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;service_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;approved_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&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;
  
  
  BaseAuthorizer
&lt;/h2&gt;



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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseAuthorizer&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;service_object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@service_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service_object&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;authorize!&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s1"&gt;'NotImplementedError'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:service_object&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it! Now you can use the Authorizer pattern in your service objects to encapsulate complex business rules and ensure that they are followed before the main logic is executed. To learn more about using the Service object pattern in Ruby on Rails, you can check out my other &lt;a href="https://dev.to/vladhilko/how-to-implement-service-object-pattern-in-ruby-on-rails-2mh8"&gt;article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So the final solution may look like this:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&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;first&lt;/span&gt;
  &lt;span class="no"&gt;UserIsActiveAuthorizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;authorize!&lt;/span&gt;

  &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Aricle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s1"&gt;'body'&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;article&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using a service object:&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;create&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;first&lt;/span&gt;

  &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CreateArticleService&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;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;call&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;article&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;The Authorizer Pattern helps us better separate logic, making it easier to test our code.&lt;/li&gt;
&lt;li&gt;It is DRY and reusable.&lt;/li&gt;
&lt;li&gt;The Authorizer Pattern allows us to avoid having a "fat" model and adhere to SOLID principles.&lt;/li&gt;
&lt;li&gt;It reduces "cognitive overhead", making the code easier to understand.&lt;/li&gt;
&lt;li&gt;It ensures that any necessary requirements are met as soon as possible by raising an error before running any form validations or business logic calculations.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
