<?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: Just The V</title>
    <description>The latest articles on Forem by Just The V (@just-the-v).</description>
    <link>https://forem.com/just-the-v</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%2F1175172%2Ffdcae0e1-efbd-4087-97f5-c83c30db678d.jpeg</url>
      <title>Forem: Just The V</title>
      <link>https://forem.com/just-the-v</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/just-the-v"/>
    <language>en</language>
    <item>
      <title>Handbook to migrate your Postgres from Heroku to Kamal</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Mon, 25 Nov 2024 08:00:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/handbook-to-migrate-your-postgres-from-heroku-to-kamal-1792</link>
      <guid>https://forem.com/just-the-v/handbook-to-migrate-your-postgres-from-heroku-to-kamal-1792</guid>
      <description>&lt;p&gt;Today I’m going to show you how I managed to migrate from Heroku to Kamal, with a focus on Postgres. The schema below describes our goal for today : &lt;/p&gt;

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

&lt;p&gt;We want : &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Preprod application, on its own host and its own Postgres instance. &lt;/li&gt;
&lt;li&gt;A Production application, with its own Postgres as well. Production also needs another feature : save DB backups on an S3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We will use my previous article as a base for today. You can find every information &lt;a href="https://dev.to/pimp_my_ruby/deploy-your-preprod-and-production-rails-application-using-kamal-139i"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Add Postgres to Production&lt;/p&gt;

&lt;p&gt;       1.1. Edit deploy.yml&lt;br&gt;
       1.2. Edit secrets&lt;br&gt;
       1.3. Edit database.yml&lt;/p&gt;

&lt;p&gt; 2. Setup Postgres for Preprod&lt;/p&gt;

&lt;p&gt;       2.1. Edit deploy.preprod.yml&lt;br&gt;
       2.2. Edit secrets&lt;br&gt;
       2.3. Edit database.yml&lt;/p&gt;

&lt;p&gt; 3. Migrate the database&lt;/p&gt;

&lt;p&gt;       3.1 Download the backup&lt;br&gt;
       3.2. Upload the backup to S3&lt;br&gt;
       3.3. Run the migration script&lt;/p&gt;

&lt;p&gt; 4. Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  Add Postgres to Production
&lt;/h2&gt;

&lt;p&gt;We already have Production and Preprod applications working perfectly without any additional services. Let’s add and configure our first accessories : Postgres and Backups !&lt;/p&gt;
&lt;h3&gt;
  
  
  Edit deploy.yml
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_production&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-db&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_MASTER_KEY&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;

&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16.4&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;190.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5432:5432&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_production&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;db-backup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eeshugerman/postgres-backup-s3:16&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;190.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;SCHEDULE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@daily'&lt;/span&gt; &lt;span class="c1"&gt;# I want a backup every day for the last 30 days&lt;/span&gt;
        &lt;span class="na"&gt;BACKUP_KEEP_DAYS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;S3_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-3&lt;/span&gt;
        &lt;span class="na"&gt;S3_PREFIX&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backups&lt;/span&gt;
        &lt;span class="na"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-backups&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-db&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_production&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;S3_ACCESS_KEY_ID&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;S3_SECRET_ACCESS_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As my service name is &lt;code&gt;my-app&lt;/code&gt;, Kamal will automatically prefix accessories with it. This is why I can freely call &lt;code&gt;my-app-db&lt;/code&gt;. Kamal understands that you’re talking about the accessory &lt;code&gt;db&lt;/code&gt; linked with the service &lt;code&gt;my-app&lt;/code&gt;. This mechanism is very powerful! &lt;/p&gt;

&lt;p&gt;For the database backups, I’ll use the marvelous image that does all the work for you : &lt;a href="https://github.com/eeshugerman/postgres-backup-s3" rel="noopener noreferrer"&gt;https://github.com/eeshugerman/postgres-backup-s3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically, you just need to set up an S3 with the correct permissions, and every day you’ll get your backups there. &lt;code&gt;postgres-backup-s3&lt;/code&gt; also provide a useful feature that allows you to restore an old backup. We will use it later on !&lt;/p&gt;
&lt;h3&gt;
  
  
  Edit secrets
&lt;/h3&gt;

&lt;p&gt;We now need to ensure that the new secrets are stored in the secrets file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .kamal/secrets
[...]
POSTGRES_PASSWORD=fakepassword
S3_ACCESS_KEY_ID=xxxxxxxxxxxxxxx
S3_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Edit database.yml
&lt;/h3&gt;

&lt;p&gt;You also need to help Rails connect to your database by providing the same environment variables:&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/database.yml&lt;/span&gt;
&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["POSTGRES_HOST"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["POSTGRES_USER"] %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV["POSTGRES_PASSWORD"] %&amp;gt;&lt;/span&gt;

&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("POSTGRES_DB") { "my_app_production" } %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it’s done, you can run :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kamal setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the Postgres container in Production. You can confirm that everything will work by accessing the dbconsole using : &lt;code&gt;kamal app exec -i 'bin/rails db'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now that we have Postgres working in Production, let’s do the same for Preprod.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup Postgres for Preprod
&lt;/h2&gt;

&lt;p&gt;Well, the steps are exactly the same. We will just provide different env variables. Let’s do it! &lt;/p&gt;

&lt;h3&gt;
  
  
  Edit deploy.preprod.yml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/deploy.preprod.yml&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-preprod&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_preprod&lt;/span&gt;
    &lt;span class="na"&gt;POSTGRES_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-preprod-db&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_MASTER_KEY&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;

&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16.4&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;190.0.0.1&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5432:5432&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
        &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_app_preprod&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
    &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only changes from Production are :&lt;/p&gt;

&lt;p&gt;— service property.&lt;br&gt;
— POSTGRES_DB and POSTGRES_HOST env variables.&lt;br&gt;
— Host domains for accessories&lt;/p&gt;
&lt;h3&gt;
  
  
  Edit secrets
&lt;/h3&gt;

&lt;p&gt;We still need to update &lt;code&gt;.kamal/secrets.preprod&lt;/code&gt; to provide a &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt; value :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .kamal/secrets.preprod
[...]
POSTGRES_PASSWORD=fakepassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Edit database.yml
&lt;/h3&gt;

&lt;p&gt;We need to add the &lt;code&gt;preprod&lt;/code&gt; environment and confirm that it takes the right environment variable.&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/database.yml&lt;/span&gt;
&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;preprod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*default&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("POSTGRES_DB") { "ride_back_preprod" } %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, we’re good to run :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kamal setup &lt;span class="nt"&gt;-d&lt;/span&gt; preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have Preprod and Prod applications working with their own database 🎉&lt;/p&gt;

&lt;p&gt;Now that everything is set up, let’s say &lt;em&gt;adios&lt;/em&gt; to Heroku and migrate our database once and for all!&lt;/p&gt;




&lt;h2&gt;
  
  
  Migrate the database
&lt;/h2&gt;

&lt;p&gt;The first thing you want to do is cut access to your application to avoid losing any data.&lt;/p&gt;

&lt;p&gt;To do so, you can enable the “Maintenance Mode” in the settings of your Heroku application.&lt;/p&gt;

&lt;p&gt;Once it’s done, we can get back to work ! &lt;/p&gt;

&lt;h3&gt;
  
  
  Download the backup
&lt;/h3&gt;

&lt;p&gt;You’ll need to find your Postgres public URL to download a backup locally. To do so, go on your application Dashboard and : &lt;/p&gt;

&lt;p&gt;“Resources” &amp;gt; “Heroku Postgres” &amp;gt; “Credentials” &amp;gt; “Default” &amp;gt; “URI”&lt;/p&gt;

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

&lt;p&gt;The URI will normally look like this : &lt;code&gt;postgres://{random-letters-and-numbers}ec2[...].eu-west-1.compute.amazonaws.com:5432/[...]&lt;/code&gt;&lt;br&gt;
Once you’ve found it, let’s download the database locally using &lt;code&gt;pg_dump&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c &lt;span class="nt"&gt;--no-acl&lt;/span&gt; &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;paste &lt;/span&gt;URI here&lt;span class="o"&gt;}&lt;/span&gt;  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; database.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see a &lt;code&gt;database.dump&lt;/code&gt; file in your directory. &lt;/p&gt;

&lt;h3&gt;
  
  
  Upload the backup to S3
&lt;/h3&gt;

&lt;p&gt;Now, you need to upload your file to the S3 bucket you use to store your backups. Use the exact same directory the backups will go. &lt;/p&gt;

&lt;h3&gt;
  
  
  Run the migration script
&lt;/h3&gt;

&lt;p&gt;Once your file is uploaded, you can now run this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kamal accessory &lt;span class="nb"&gt;exec &lt;/span&gt;db-backup &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"sh"&lt;/span&gt; 
&lt;span class="nb"&gt;source&lt;/span&gt; ./env.sh
&lt;span class="nv"&gt;s3_uri_base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3_BUCKET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;S3_PREFIX&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
aws &lt;span class="nv"&gt;$aws_args&lt;/span&gt; s3 &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;s3_uri_base&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/database.dump"&lt;/span&gt; &lt;span class="s2"&gt;"database.dump"&lt;/span&gt;
&lt;span class="nv"&gt;conn_opts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-h &lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_HOST&lt;/span&gt;&lt;span class="s2"&gt; -p &lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_PORT&lt;/span&gt;&lt;span class="s2"&gt; -U &lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_USER&lt;/span&gt;&lt;span class="s2"&gt; -d &lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_DATABASE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
pg_restore &lt;span class="nv"&gt;$conn_opts&lt;/span&gt; &lt;span class="nt"&gt;--clean&lt;/span&gt; &lt;span class="nt"&gt;--if-exists&lt;/span&gt; &lt;span class="nt"&gt;--no-acl&lt;/span&gt; &lt;span class="nt"&gt;--no-owner&lt;/span&gt; database.dump
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; database.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script will: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Access to the database backup container using &lt;code&gt;sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Download your &lt;code&gt;database.dump&lt;/code&gt; file using aws cli &lt;/li&gt;
&lt;li&gt;Restore the database based on &lt;code&gt;database.dump&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;database.dump&lt;/code&gt; to free space.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Your database is now completely self-hosted. Congratulations!&lt;/p&gt;

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

&lt;p&gt;Thanks for reading so far !&lt;/p&gt;

&lt;p&gt;If you need help with your Kamal configuration, do not hesitate to comment out or reach me by DM 👋&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>devops</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Deploy your Preprod and Production Rails Application using Kamal</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Wed, 13 Nov 2024 07:30:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/deploy-your-preprod-and-production-rails-application-using-kamal-139i</link>
      <guid>https://forem.com/just-the-v/deploy-your-preprod-and-production-rails-application-using-kamal-139i</guid>
      <description>&lt;p&gt;In most of the projects I contributed to, the deployment workflow looked like this :&lt;/p&gt;

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

&lt;p&gt;We got a Preprod application that lets us experiment and do some QA. Preprod is an application that I can break without impacting users. Once I know that my Preprod application is working correctly, I can then deploy my application to the Production environment and expose new features to users.&lt;/p&gt;

&lt;p&gt;Usually, I used Heroku for both Preprod and Prod. But… come on, we’re in 2024 right ? Heroku is old school, now we’re using Kamal 😎&lt;/p&gt;

&lt;p&gt;Today I’ll share with you how I manage the deployment of multienvironment application using Kamal 2 in a step-by-step guide. &lt;/p&gt;

&lt;p&gt;I won’t go that deep on how to configure kamal, I’ll just share the specificities of handling 2 environments deployment. If you need more details about a certain part, I linked useful resources that helped me and will help you for sure in the conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare your Preprod environment
&lt;/h2&gt;

&lt;p&gt;Before toying with Kamal, we need to create the Preprod environment.&lt;/p&gt;

&lt;p&gt;To do so, I only need to create a new file in my environments folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;config/environments/production.rb config/environments/preprod.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want my Preprod environment to be as close as possible to my Prod environment, so I won’t edit the file.&lt;/p&gt;

&lt;p&gt;Now that Rails knows my Preprod environment, I need to create a &lt;code&gt;master.key&lt;/code&gt; for this environment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails credentials:edit &lt;span class="nt"&gt;-e&lt;/span&gt; preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last thing, we need a dedicated Dockerfile to handle this specific environment when building the Docker image.&lt;/p&gt;

&lt;p&gt;For the sake of this article, I will take the default Dockerfile provided by Rails when you’re using &lt;code&gt;rails new&lt;/code&gt;, and create a dedicated one for Preprod environment :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;Dockerfile Dockerfile.preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing I will edit will be the ENV variables set by &lt;code&gt;Dockerfile.preprod&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.preprod L20&lt;/span&gt;
ENV &lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"preprod"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_DEPLOYMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bundle"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_WITHOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it, now we’re good to explore Kamal !!&lt;/p&gt;

&lt;p&gt;Your folder architecture will look like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── Dockerfile
├── Dockerfile.preprod
└── config
    ├── credentials
    │   ├── preprod.key
    │   └── preprod.yml.enc
    ├── credentials.yml.enc
    ├── environments
    │   ├── preprod.rb
    │   └── production.rb
    └── master.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Kamal
&lt;/h2&gt;

&lt;p&gt;Kamal is not a gem that you add to your Gemfile, but you can install it using &lt;code&gt;gem&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;kamal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it’s done, you have access to Kamal. You can use the following command to bootstrap your Kamal configuration :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kamal init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kamal init&lt;/code&gt; will create a &lt;code&gt;config/deploy.yml&lt;/code&gt; file and a &lt;code&gt;.kamal&lt;/code&gt; folder containing a hooks directory and a &lt;code&gt;secrets&lt;/code&gt; file&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Prod
&lt;/h2&gt;

&lt;p&gt;I will keep my &lt;code&gt;config/deploy.yml&lt;/code&gt; as simple as possible, here is what I write :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# config/deploy.yml&lt;/span&gt;
service: my-app
image: docker-username/my-app

servers:
  web:
    - 190.0.0.0

proxy:
  ssl: &lt;span class="nb"&gt;true
  &lt;/span&gt;host: mydomain.com
  app_port: 3000

registry:
  username: docker-username 
  password:
    - KAMAL_REGISTRY_PASSWORD

builder:
  &lt;span class="nb"&gt;arch&lt;/span&gt;: arm64
  &lt;span class="nb"&gt;local&lt;/span&gt;: &lt;span class="nb"&gt;true
  &lt;/span&gt;dockerfile: Dockerfile.production
  context: &lt;span class="s2"&gt;"."&lt;/span&gt;

&lt;span class="nb"&gt;env&lt;/span&gt;:
  secret:
    - RAILS_MASTER_KEY

ssh:
  user: ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s dive into each parameter : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;service&lt;/strong&gt;: The name of your application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;image&lt;/strong&gt;: The name of the image you will use for your registry (I’ll use a public docker registry here)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;servers&lt;/strong&gt;: The addresses of the servers you will deploy your application. Here I have a single host for my production. I can also add a dedicated process for jobs handling if I need to process job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proxy&lt;/strong&gt;: handle SSL certificate and point to the right domain name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;registry&lt;/strong&gt;: where your Docker image will be pushed / pulled&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;builder&lt;/strong&gt;: Use Dockerfile.production and build it locally when deploying to the server. I use arm64 architecture because I’m using a macBook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;env&lt;/strong&gt;: It’s a way of passing secrets to the machine. We’ll talk about it just after&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ssh&lt;/strong&gt;: Kamal will connect to the servers using the &lt;code&gt;ubuntu&lt;/code&gt; user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To provide secrets, Kamal introduces it own secrets mechanism. You need to edit &lt;code&gt;.kamal/secrets&lt;/code&gt; to add the &lt;code&gt;RAILS_MASTER_KEY&lt;/code&gt; environment variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .kamal/secrets&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_MASTER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/master.key&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysuperpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it’s done, you’re good to either setup your server or deploy your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kamal setup
&lt;span class="nv"&gt;$ &lt;/span&gt;kamal deploy &lt;span class="c"&gt;# if you want to deploy only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now your application is running in production and fully accessible at your domain 🎉&lt;/p&gt;

&lt;p&gt;At this stage, your folder architecture will look like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── .kamal
│   └── secrets
├── Dockerfile
├── Dockerfile.preprod
└── config
    ├── credentials
    │   ├── preprod.key
    │   └── preprod.yml.enc
    ├── credentials.yml.enc
    ├── deploy.yml
    ├── environments
    │   ├── preprod.rb
    │   └── production.rb
    └── master.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see how to setup Preprod deployment now !&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Preprod
&lt;/h2&gt;

&lt;p&gt;To configure Preprod deployment, we need to replicate what we did for Production, and essentially add &lt;code&gt;.preprod&lt;/code&gt; everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .kamal/secrets .kamal/secrets.preprod
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;config/deploy.yml config/deploy.preprod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then in the &lt;code&gt;config/deploy.preprod.yml&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# config/deploy.preprod.yml&lt;/span&gt;
image: docker-username/my-app-preprod

servers:
  web:
    - 190.0.0.1

proxy:
  host: preprod.mydomain.com

builder:
  dockerfile: Dockerfile.preprod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And … that’s it.&lt;/p&gt;

&lt;p&gt;Kamal will read the “primary” config (&lt;code&gt;deploy.yml&lt;/code&gt;) as a fallback for missing value in our &lt;code&gt;deploy.preprod.yml&lt;/code&gt;. We’re just saying to Kamal that it must take the &lt;code&gt;Dockerfile.preprod&lt;/code&gt; and set the SSL certificate will point to &lt;a href="http://preprod.mydomain.com" rel="noopener noreferrer"&gt;&lt;code&gt;preprod.mydomain.com&lt;/code&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The last thing to do before deploying is to edit the secrets :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .kamal/secrets.preprod&lt;/span&gt;
&lt;span class="nv"&gt;RAILS_MASTER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/credentials/preprod.key&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;KAMAL_REGISTRY_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysuperpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then you’re good to go :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kamal setup &lt;span class="nt"&gt;-d&lt;/span&gt; preprod
&lt;span class="nv"&gt;$ &lt;/span&gt;kamal deploy &lt;span class="nt"&gt;-d&lt;/span&gt; preprod &lt;span class="c"&gt;# if you want to deploy only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application can be accessed at &lt;a href="http://preprod.mydomain.com" rel="noopener noreferrer"&gt;&lt;code&gt;preprod.mydomain.com&lt;/code&gt;&lt;/a&gt; !&lt;/p&gt;

&lt;p&gt;Your folder architecture will look like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
├── .kamal
│   ├── secrets
│   └── secrets.preprod
├── Dockerfile
├── Dockerfile.preprod
└── config
    ├── credentials
    │   ├── preprod.key
    │   └── preprod.yml.enc
    ├── credentials.yml.enc
    ├── deploy.preprod.yml
    ├── deploy.yml
    ├── environments
    │   ├── preprod.rb
    │   └── production.rb
    └── master.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ressources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kamal-deploy.org/docs/installation/" rel="noopener noreferrer"&gt;Kamal documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://guillaumebriday.fr/how-to-deploy-and-scale-your-rails-application-with-kamal" rel="noopener noreferrer"&gt;Less abstract guilde if you need to setup Database / Redis / Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I took a lot of shortcuts here, but the point is that it’s very easy and painless to deploy multienvironment applications using Kamal.&lt;/p&gt;

&lt;p&gt;So far, I only have good experience using Kamal. I've never had any difficult stages while debugging / monitoring, so it’s a very positive thing.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>devops</category>
      <category>heroku</category>
    </item>
    <item>
      <title>📩My journey to send 100 mails to 500k effortlessly📩</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Mon, 04 Nov 2024 07:30:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/my-journey-to-send-100-mails-to-500k-effortlessly-1ne7</link>
      <guid>https://forem.com/just-the-v/my-journey-to-send-100-mails-to-500k-effortlessly-1ne7</guid>
      <description>&lt;p&gt;Today, I’d like to share my journey while working for a client on implementing an efficient mailing system.&lt;/p&gt;

&lt;p&gt;We'll see how I enabled my client to send 100, 1,000, and even 500,000 emails without impacting the performance of my Rails application.&lt;/p&gt;

&lt;p&gt;Let's dive in now 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. The Mail&lt;br&gt;
 2. V1: Deliver Now (up to 300 emails)&lt;br&gt;
 3. V2: Deliver Later (up to 100,000 emails)&lt;br&gt;
 4. V3: Perform All Later (up to +500,000 emails)&lt;br&gt;
 5. Other possible alternatives I explored&lt;br&gt;
       5.1. Monkey Patching Application Mailer&lt;br&gt;
       5.2. Using the Mail Provider’s Native Bulk Send&lt;br&gt;
       5.3. Using BCC&lt;br&gt;
 6. Conclusion&lt;/p&gt;


&lt;h2&gt;
  
  
  The Mail
&lt;/h2&gt;

&lt;p&gt;For the project example, we'll start with a simple mailer, adding a method to send emails that simply forwards the arguments. Here's the 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;# app/mailers/application_mailer.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generic_mail&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="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;subject: &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;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Call the mailer like this:&lt;/span&gt;
&lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generic_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;How are you today?&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"you@gmail.com"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to expose an interface to my admin so they can send emails to our entire user base. Here's an example of what the interface might look like:&lt;/p&gt;

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

&lt;p&gt;The challenge is ensuring that the emails are sent promptly, ideally within 10 seconds.&lt;/p&gt;

&lt;p&gt;Let's explore the different iterations of sending emails!&lt;/p&gt;

&lt;h2&gt;
  
  
  V1: Deliver Now (up to 300 emails)
&lt;/h2&gt;

&lt;p&gt;The simplest way to send emails in Rails → &lt;code&gt;deliver_now&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="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&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;email&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generic_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;How are you today?&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, we only had a Rails application and a database. Given our small user base, using &lt;code&gt;deliver_now&lt;/code&gt; was acceptable.&lt;/p&gt;

&lt;p&gt;The problem with &lt;code&gt;deliver_now&lt;/code&gt; is that it’s a blocking call. It waits for a response from the mail provider before moving to the next line of code.&lt;/p&gt;

&lt;p&gt;Assuming each API call takes around 100ms, sending 100 emails would mean a 10-second wait on the interface. This solution is thus only suitable for testing scenarios with a minimal database.&lt;/p&gt;

&lt;h2&gt;
  
  
  V2: Deliver Later (up to 100,000 emails)
&lt;/h2&gt;

&lt;p&gt;The logical next step to accelerate the application is to send emails within a Job. ActionMailer offers the &lt;code&gt;deliver_later&lt;/code&gt; method for 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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&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;email&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generic_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;How are you today?&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;deliver_later&lt;/code&gt; enqueues a &lt;code&gt;MailDeliveryJob&lt;/code&gt; with all the mail information.&lt;/p&gt;

&lt;p&gt;We were satisfied with this for a while. However, as our email volume increased, Redis started to be overwhelmed with write operations.&lt;/p&gt;

&lt;p&gt;Despite Redis being very fast, sending around 10,000 emails led to slowdowns, likely due to our $3/month instance on Heroku.&lt;/p&gt;

&lt;p&gt;Fortunately, I found an even more efficient solution for handling higher volumes of emails!&lt;/p&gt;

&lt;h2&gt;
  
  
  V3: Perform All Later (up to +500,000 emails)
&lt;/h2&gt;

&lt;p&gt;Rails 7.1 introduced a powerful feature called &lt;code&gt;ActiveJob.perform_all_later&lt;/code&gt;, enabling bulk job enqueuing. The downside is that we need to move away from &lt;code&gt;mail.deliver_later&lt;/code&gt; and use a custom job class. Here's a simple 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;SendEmailJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generic_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;subject: &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;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&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;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email&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;email&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;SendEmailJob&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;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;p&amp;gt;How are you today?&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;email&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;ApplicationJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_all_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this implementation, we managed to send emails to 500,000 users in approximately 15 seconds. Pretty impressive!&lt;/p&gt;

&lt;p&gt;Based on my benchmarks, this method could potentially handle sending 1,000,000 emails in about 40 seconds. Unfortunately, Heroku imposes a page timeout after 30 seconds, so another solution will be necessary when I will need to send a million.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other possible alternatives I explored
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monkey Patching Application Mailer
&lt;/h3&gt;

&lt;p&gt;Seeing the performance of &lt;code&gt;ApplicationJob.perform_all_later&lt;/code&gt; led me to consider monkey-patching &lt;code&gt;ApplicationMailer&lt;/code&gt; to implement a &lt;code&gt;deliver_all_later&lt;/code&gt; method. Here is my 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;ApplicationMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&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;deliver_all_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_job&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_handler&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_get&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;to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'deliver_now'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;args: &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:@args&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;options&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;ActiveJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_all_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&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 theory, this seemed promising. However, my benchmarks showed inefficiencies beyond 100,000 emails due to memory bloat from handling many &lt;code&gt;MessageDelivery&lt;/code&gt; objects. Thus, it wasn’t the best path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Mail Provider’s Native Bulk Send
&lt;/h3&gt;

&lt;p&gt;Some providers like &lt;a href="https://dev.mailjet.com/email/guides/send-api-v31/#send-in-bulk" rel="noopener noreferrer"&gt;Mailjet&lt;/a&gt; offer native bulk sending. This could be an option in the future if our email volume continues to grow, although it requires using their SDK over &lt;code&gt;ApplicationMailer&lt;/code&gt;, making the code less Rails-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using BCC
&lt;/h3&gt;

&lt;p&gt;An easy idea is to use BCC, sending the same email to many recipients. This would work if the email content is identical for everyone. However, my custom mail system interprets user-specific variables, so this wasn't viable for my use case.&lt;/p&gt;




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

&lt;p&gt;That’s it, thanks for reading me so far. If you find a better way to send more and more mails I would love to hear from you. Don’t hesitate to comment if you want more details about my searches / benchmark.&lt;/p&gt;

&lt;p&gt;Bye 🕺&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>🚩Effortless Feature Management in Rails : A Guide to Using Flipper with Active Admin</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Tue, 03 Sep 2024 07:00:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/effortless-feature-management-in-rails-a-guide-to-using-flipper-with-active-admin-51b4</link>
      <guid>https://forem.com/just-the-v/effortless-feature-management-in-rails-a-guide-to-using-flipper-with-active-admin-51b4</guid>
      <description>&lt;p&gt;Feature flags are development tools that allow you to control the activation and deactivation of certain features in your application during runtime.&lt;/p&gt;

&lt;p&gt;They are extremely useful for testing new features in production or for performing progressive deployments, all without redeploying the code.&lt;/p&gt;

&lt;p&gt;Today, we will explore how to implement feature flags in your Ruby on Rails application by integrating Flipper into your Active Admin.&lt;/p&gt;

&lt;p&gt;All the code shown today is accessible in &lt;a href="https://github.com/just-the-v/flipper-activeadmin" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Feature Flags Management&lt;br&gt;
 2. Use a Feature Flag in Your Code&lt;br&gt;
 3. How I Integrate Flipper UI in ActiveAdmin&lt;br&gt;
       3.1. 1. Only Admins have access to Flipper UI&lt;br&gt;
       3.2. 2. Flipper UI must be accessible on the Admin Panel&lt;br&gt;
       3.3. 3. Admin Panel must be accessible on the Flipper UI&lt;br&gt;
 4. Conclusion&lt;/p&gt;


&lt;h2&gt;
  
  
  Feature Flags Management
&lt;/h2&gt;

&lt;p&gt;To add feature flags to your application, we will use the Flipper ecosystem.&lt;/p&gt;

&lt;p&gt;For this, we will need three gems: &lt;code&gt;flipper&lt;/code&gt;, &lt;code&gt;flipper-ui&lt;/code&gt;, and &lt;code&gt;flipper-active_record&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; bundle add flipper flipper-ui flipper-active_record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To store the feature flags in your database, we need to tell Flipper to use ActiveRecord. To do this, run the Flipper setup command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; rails g flipper:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to create and modify our feature flags graphically, we just need to expose &lt;code&gt;flipper-ui&lt;/code&gt; routes. Flipper UI will expose a web interface for managing our 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;# config/routes.rb&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;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;'/feature-flags'&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 voilà 🎉, you have configured Flipper in your application. You can now access the UI via the &lt;code&gt;/feature-flags&lt;/code&gt; route by starting your server.&lt;/p&gt;

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

&lt;p&gt;Before configuring the integration with ActiveAdmin, let’s create our first feature flag together.&lt;/p&gt;

&lt;p&gt;It will be called &lt;code&gt;new_admin_dashboard&lt;/code&gt; and is activated at 50% for all users.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Use a Feature Flag in Your Code
&lt;/h2&gt;

&lt;p&gt;Let’s assume we have an application using ActiveAdmin. On the root of your Active Admin Panel, you will find the default dashboard. Here is mine:&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/admin/dashboard.rb&lt;/span&gt;

&lt;span class="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_page&lt;/span&gt; &lt;span class="s2"&gt;"Dashboard"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;menu&lt;/span&gt; &lt;span class="ss"&gt;priority: &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;label: &lt;/span&gt;&lt;span class="s1"&gt;'Dashboard'&lt;/span&gt;

  &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"'Dashboard' do"&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"blank_slate_container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"dashboard_default_message"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"blank_slate"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to ActiveAdmin.'&lt;/span&gt;
        &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="s2"&gt;"You're looking at the default dashboard."&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;We will implement new wording in our dashboard, which will only be exposed via the &lt;code&gt;new_admin_dashboard&lt;/code&gt; feature flag.&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/admin/dashboard.rb&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"blank_slate"&lt;/span&gt; &lt;span class="k"&gt;do&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="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new_admin_dashboard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to the new ActiveAdmin dashboard!'&lt;/span&gt;
    &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="s2"&gt;"You're looking pretty cool! Have a nice day buddy!"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to ActiveAdmin.'&lt;/span&gt;
    &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="s2"&gt;"You're looking at the default dashboard."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature flag is now in place. When I refresh the page, I have a 50/50 chance of seeing my new wording:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  How I Integrate Flipper UI in ActiveAdmin
&lt;/h2&gt;

&lt;p&gt;Flipper UI is really cool! But in order to expose it to non-technical users, it’s important to link your application with Flipper UI.&lt;/p&gt;

&lt;p&gt;In the projects where I had to integrate Flipper, I also had Active Admin, which allows non-technical users to interact with the application.&lt;/p&gt;

&lt;p&gt;Here are a few points on how I link the two tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Only Admins have access to Flipper UI
&lt;/h3&gt;

&lt;p&gt;To allow only admins to access Flipper UI, we will leverage Active Admin authentication. For this, simply modify the way we declare our route to Flipper UI:&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;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="no"&gt;ActiveAdmin&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="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;devise_for&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;ActiveAdmin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;
  &lt;span class="n"&gt;authenticate&lt;/span&gt; &lt;span class="ss"&gt;:admin&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;'/admin/feature-flags'&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 there you go! Our Flipper UI is now behind the Active Admin authentication wall and is accessible at the &lt;code&gt;/admin/feature-flags&lt;/code&gt; route.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Flipper UI must be accessible on the Admin Panel
&lt;/h3&gt;

&lt;p&gt;Only admins have permission to access Flipper UI. But they still need to find the access!&lt;/p&gt;

&lt;p&gt;Fortunately, Active Admin offers a very simple solution:&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/active_admin.rb&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:admin&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;admin&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build_menu&lt;/span&gt; &lt;span class="ss"&gt;:default&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;menu&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;menu&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;label: &lt;/span&gt;&lt;span class="s1"&gt;'Feature Flags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="s1"&gt;'/admin/feature-flags'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s all 🎉&lt;/p&gt;

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

&lt;h3&gt;
  
  
  3. Admin Panel must be accessible on the Flipper UI
&lt;/h3&gt;

&lt;p&gt;Once on the Flipper UI interface, I want to be able to easily return to the admin page. Fortunately, Flipper UI exposes an option in its configuration just for this!&lt;/p&gt;

&lt;p&gt;We will create an initializer file to hold the Flipper configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/flipper.rb&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;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;application_breadcrumb_href&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration allows Flipper UI to display a small “&amp;lt; App” button at the top of its navigation menu.&lt;/p&gt;

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

&lt;p&gt;Now we have a link from the admin to Flipper, and from Flipper back to the admin!&lt;/p&gt;




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

&lt;p&gt;Setting up &lt;strong&gt;feature flags&lt;/strong&gt; with Flipper and Active Admin offers great flexibility in managing the activation of new features in production while minimizing risks.&lt;/p&gt;

&lt;p&gt;This integration allows both technical and non-technical teams to collaborate more effectively by providing tools tailored to their needs.&lt;/p&gt;

&lt;p&gt;We have covered a very simple use case of Flipper, but in reality, this tool is quite powerful and capable of more advanced calculations than just “Displaying this dashboard 50% of the time.”&lt;/p&gt;

&lt;p&gt;Thank you for reading, and see you soon in the next article!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Make your Rails App Configurable in 4 Ways</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Thu, 01 Aug 2024 07:00:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/make-your-rails-app-configurable-in-4-ways-h5k</link>
      <guid>https://forem.com/just-the-v/make-your-rails-app-configurable-in-4-ways-h5k</guid>
      <description>&lt;p&gt;Over the years, I have participated in several Ruby on Rails projects that implement the same functionalities but in completely different ways.&lt;/p&gt;

&lt;p&gt;Today, I would like to share with you the 4 ways I have found most effective for making the projects I have worked on &lt;strong&gt;Configurable&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Configuring an application means giving administrators the ability to provide the context your application needs to operate.&lt;/p&gt;

&lt;p&gt;Let's say your application has a paid subscription. As a developer, you want to "give control" to administrators to update prices. You want to be able to call a "variable" directly in the code, which is accessible everywhere. This is how you will make your application configurable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;  1. Constants&lt;br&gt;
  2. Configuration File&lt;br&gt;
  3. Configuration Model&lt;br&gt;
  4. Variable Model&lt;br&gt;
  TL;DR&lt;br&gt;
  Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Constants
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;The simplest and most naive way to make your application configurable is by assigning constants to your classes.&lt;/p&gt;

&lt;p&gt;There are generally two types of constants:&lt;/p&gt;

&lt;p&gt;Global constants, which you will use in multiple places 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;# config/initializers/global.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Global&lt;/span&gt;
  &lt;span class="no"&gt;SOFTWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[libreoffice word textedit]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
  &lt;span class="no"&gt;BRAND_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"YourNameHere"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# You can use it everywhere in your app like this&lt;/span&gt;
&lt;span class="no"&gt;Global&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BRAND_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"YourNameHere"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or constants that are specialized for a feature or 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;UpdateUserFirstNameService&lt;/span&gt;
  &lt;span class="no"&gt;FORBIDDEN_FIRST_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Julien"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;first_name&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;'Forbidden first name'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;FORBIDDEN_FIRST_NAME&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;first_name: &lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These constants are directly accessible throughout the application without going through a database or external files.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity and Speed&lt;/strong&gt;: Immediate access to configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Fewer risks of accidental modifications since constants are defined in code and not modifiable via an admin interface.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Flexibility&lt;/strong&gt;: Any changes require code modification and redeployment of the application, which can be cumbersome for configurations that may need to change more regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Suitable for Dynamic Configurations&lt;/strong&gt;: Not practical for data that needs to be updated regularly or is too voluminous to be efficiently managed as constants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Administrative Control&lt;/strong&gt;: Non-technical administrators cannot update configurations without going through developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use It?
&lt;/h3&gt;

&lt;p&gt;In my opinion, this approach should only be used when you want to configure static elements. Examples include API keys, values useful throughout your application, or data for static pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Configuration File
&lt;/h2&gt;

&lt;p&gt;A slightly more advanced way to configure your application is via configuration files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;In a &lt;code&gt;.yml&lt;/code&gt; or &lt;code&gt;.csv&lt;/code&gt; file, you can store all the data you need in a hierarchical format. You can then exploit this data by reading it when the application starts. We then need to expose these values to our application through a class that contains a constant.&lt;/p&gt;

&lt;p&gt;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="c1"&gt;# config/app/airports.yml&lt;/span&gt;
&lt;span class="no"&gt;AYHK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Hoskins&lt;/span&gt; &lt;span class="no"&gt;Airport&lt;/span&gt;
  &lt;span class="ss"&gt;country: &lt;/span&gt;&lt;span class="no"&gt;US&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="no"&gt;EFVR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;Varkaus&lt;/span&gt; &lt;span class="no"&gt;Airport&lt;/span&gt;
  &lt;span class="ss"&gt;country: &lt;/span&gt;&lt;span class="no"&gt;FI&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="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# app/services/find_airport_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FindAirportService&lt;/span&gt;
  &lt;span class="no"&gt;AIRPORTS&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;safe_load&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="s2"&gt;"config/app/airports.yml"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_indifferent_access&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;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;from_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;AIRPORTS&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="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;from_country&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="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;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer Control&lt;/strong&gt;: Modifications are controlled by developers, reducing the risk of accidental errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Quick reading of configurations at startup without database queries.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less Flexible for Administrators&lt;/strong&gt;: All changes require developer intervention and application restart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Management Difficulties for Large Data Sets&lt;/strong&gt;: Complications can arise when information is numerous or when data structures evolve.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use It?
&lt;/h3&gt;

&lt;p&gt;We already use &lt;code&gt;.yml&lt;/code&gt; files in our Rails projects for &lt;code&gt;I18n&lt;/code&gt;. Configuration files are very useful for storing data that we don't want in the database but is too numerous for simple constants.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Configuration Model
&lt;/h2&gt;

&lt;p&gt;Now that we've seen static configurations, let's look at dynamic configurations by introducing the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;We'll set up a &lt;code&gt;Configuration&lt;/code&gt; model. Each attribute of this model corresponds to a specific variable. We could represent the table 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;# inside db/schema.rb&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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;float&lt;/span&gt; &lt;span class="s2"&gt;"variable_1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mf"&gt;5.0&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;integer&lt;/span&gt; &lt;span class="s2"&gt;"variable_2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;160&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"admin_id"&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;boolean&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &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;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;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"admin_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_configurations_on_admin_id"&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;admin_id&lt;/strong&gt;: Identifies who set up the configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;active&lt;/strong&gt;: Identifies the configuration to use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;variable_1&lt;/strong&gt; and &lt;strong&gt;variable_2&lt;/strong&gt;: Variables stored in the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is to have a &lt;code&gt;Configuration&lt;/code&gt; model that exposes quick and lightweight access for the application.&lt;/p&gt;

&lt;p&gt;Here's how I like to implement 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/configuration.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Configuration&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;LOOKABLE_ATTRIBUTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%i[
    variable_1
    variable_2
  ]&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;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;active&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;cache&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;'active_configuration'&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;Configuration&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;active: &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;to_h&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;to_h&lt;/span&gt;
      &lt;span class="no"&gt;LOOKABLE_ATTRIBUTES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index_with&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;attribute&lt;/span&gt;&lt;span class="o"&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;attribute&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;LOOKABLE_ATTRIBUTES&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;attribute&lt;/span&gt;&lt;span class="o"&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;attribute&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;active&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="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="c1"&gt;# rails console &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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variable_1&lt;/span&gt;
&lt;span class="no"&gt;Configuration&lt;/span&gt; &lt;span class="no"&gt;Load&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.9&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="no"&gt;SELECT&lt;/span&gt; &lt;span class="s2"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;*&lt;/span&gt; &lt;span class="no"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;"configurations"&lt;/span&gt; &lt;span class="no"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;"configurations"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$1&lt;/span&gt; &lt;span class="no"&gt;LIMIT&lt;/span&gt; &lt;span class="vg"&gt;$2&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&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="s2"&gt;"LIMIT"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variable_2&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I just need to expose my Configuration model through the admin interface. When an admin wants to update the Configuration, a new record is created. This record has &lt;code&gt;active: true&lt;/code&gt;, and it invalidates the cache in the process.&lt;/p&gt;

&lt;p&gt;If at any point there's a configuration issue, I can always roll back simply by modifying the &lt;code&gt;active&lt;/code&gt; attribute.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Control and Flexibility&lt;/strong&gt;: Administrators are autonomous while developers retain code control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and Traceability&lt;/strong&gt;: Each change can be traced to a specific admin, and configurations can be easily activated or deactivated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good Performance&lt;/strong&gt;: Uses caching to avoid repetitive database queries, thus improving performance.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache Management&lt;/strong&gt;: Requires an effective strategy for cache invalidation when configurations are updated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adding Variables is Cumbersome&lt;/strong&gt;: To add a variable in your Configuration model, you need to: Make a migration, update your model, update your admin interface, and deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use It?
&lt;/h3&gt;

&lt;p&gt;Perfect for environments where configurations need to be frequently updated by administrators, but adding variables is rare.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Variable Model
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Configuration&lt;/code&gt; model has a limitation: adding variables is restricted by the developer.&lt;/p&gt;

&lt;p&gt;Let's see how to break this glass ceiling with the &lt;code&gt;Variable&lt;/code&gt; model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;p&gt;This method uses a &lt;code&gt;Variable&lt;/code&gt; model where each record represents a different variable. Let's see its structure right away, and I'll explain afterward:&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/schema.rb&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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="s2"&gt;"name"&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;string&lt;/span&gt; &lt;span class="s2"&gt;"value"&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;string&lt;/span&gt; &lt;span class="s2"&gt;"type"&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;boolean&lt;/span&gt; &lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="kp"&gt;false&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attribute names are quite clear. Let's look at an example of implementation I use for this approach:&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/variable.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Variable&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;enum&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;string: &lt;/span&gt;&lt;span class="s1"&gt;'string'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;integer: &lt;/span&gt;&lt;span class="s1"&gt;'integer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;float: &lt;/span&gt;&lt;span class="s1"&gt;'float'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;boolean: &lt;/span&gt;&lt;span class="s1"&gt;'boolean'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StringResolver&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;resolve&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;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;IntegerResolver&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FloatResolver&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooleanResolver&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&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;fetch&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;variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_by!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &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;resolver&lt;/span&gt; &lt;span class="o"&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;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&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;Resolver"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constantize&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;
        &lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="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="c1"&gt;# rails console &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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Variable&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="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"float"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"5.34,43.4,10.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;array: &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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Variable&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="s2"&gt;"foo"&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="mf"&gt;5.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;43.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very simple version of the Variable model. Another implementation could allow requesting multiple Variables at once, using a LIKE query for example.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully Administered by Admin&lt;/strong&gt;: Allows flexible management of configurations without developer intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Administrators can add, modify, or delete configurations on the fly.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Risk of Errors&lt;/strong&gt;: High risk of&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;human error, such as a typo in the variable name, which can lead to hard-to-detect bugs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Can impact performance if configurations are frequently queried without effective caching. This can quickly become a memory sink.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to Use It?
&lt;/h3&gt;

&lt;p&gt;I really like this approach for generalizing code aspects. It allows me to ignore the context beforehand and still develop quite advanced features. I also like that I often have to use a lot of meta-programming to exploit it to its full potential.&lt;/p&gt;

&lt;p&gt;However, there are often errors because the admin or developer forgot to create or misspelled a variable.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you're a bit lost with all these methods, I've prepared a simple questionnaire to help you decide when to use which method.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do configurations need to be modified?

&lt;ul&gt;
&lt;li&gt;No: &lt;strong&gt;Constants&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Do administrators need to modify configuration variables without developer intervention?

&lt;ul&gt;
&lt;li&gt;No: &lt;strong&gt;Configuration File&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Do administrators need full control over the configuration?

&lt;ul&gt;
&lt;li&gt;Yes: &lt;strong&gt;Variable Model&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No: &lt;strong&gt;Configuration Model&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In summary, choosing the right method to configure your application depends on the frequency of changes, who controls these changes, and the importance of flexibility.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>The easiest guide to generate PDF📜 using Prawn in your Rails project</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Thu, 25 Jul 2024 07:00:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/the-easiest-guide-to-generate-pdf-using-prawn-in-your-rails-project-oh3</link>
      <guid>https://forem.com/just-the-v/the-easiest-guide-to-generate-pdf-using-prawn-in-your-rails-project-oh3</guid>
      <description>&lt;p&gt;By the end of this guide, you will be able to generate a PDF on demand using the Prawn gem in your Ruby on Rails project!&lt;/p&gt;

&lt;p&gt;PDF generation is a feature that I have often been asked for when developing Ruby on Rails projects. Let's see how I like to implement it in my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. What is Prawn?&lt;br&gt;
    1.1. Managing the Cursor&lt;br&gt;
    1.2. Managing Tables&lt;br&gt;
 2. Let’s build something together!&lt;br&gt;
 3. Pros and Cons of Using Prawn&lt;br&gt;
    3.3. Pros&lt;br&gt;
    3.4. Cons&lt;br&gt;
 4. Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Prawn?
&lt;/h2&gt;

&lt;p&gt;Prawn is a fast and lightweight Ruby library for generating PDFs. It has no external dependencies, which means you only need to install the gem in your 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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;prawn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generating a PDF file is very simple. Prawn exposes a very clear API for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Prawn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello_world.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the code is executed, we have a beautiful PDF file "hello_world.pdf" that indeed contains the wording "Hello World!".&lt;/p&gt;

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

&lt;p&gt;Writing text in a PDF is great, but you can do much more with Prawn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing the Cursor
&lt;/h3&gt;

&lt;p&gt;When we create a PDF document, Prawn exposes us "Cursor". It's our guide to know where we are in the document we are creating.&lt;/p&gt;

&lt;p&gt;As you saw in the example above, the text naturally appeared at the top of my page.&lt;/p&gt;

&lt;p&gt;If I add other text blocks, they will follow each other, skipping a line:&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;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Prawn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello_world.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vjwwwzs50nz41ywha19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vjwwwzs50nz41ywha19.png" alt="4 lines of Hello World! in a PDF" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks to this notion of cursor, we can change the layout of the elements using all the cursor 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="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Prawn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke_horizontal_rule&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pad&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Full padding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke_horizontal_rule&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_down&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke_horizontal_rule&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pad_bottom&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Padding Bottom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke_horizontal_rule&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_up&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Last text but display before"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_cursor_to&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Origin of the document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;style: :bold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello_world.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftb6mvb4o7vuhuh9lu747.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftb6mvb4o7vuhuh9lu747.png" alt="Rendered PDF file of the code below" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few additional explanations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;stroke_horizontal_rule&lt;/strong&gt;: allows you to draw a horizontal line. Useful to see the impact of padding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pad&lt;/strong&gt;: Short for padding, allows you to add space above and below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pad_bottom&lt;/strong&gt;: Short for padding-bottom, allows you to add space below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;move_up&lt;/strong&gt;: Moves the cursor up from its position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;move_cursor_to&lt;/strong&gt;: Moves the cursor exactly to the specified line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these helpers are very practical for placing our content on the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Tables
&lt;/h3&gt;

&lt;p&gt;Creating tables is a very common use case when it comes to PDF generation. Let's see how to create a basic table:&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;# Prawn supports basic HTML tags. I use them to style my table headers&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[Name Age City]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;font size='12'&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;data&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="s1"&gt;'John'&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="s1"&gt;'New York'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Jane'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'London'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Paris'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Prawn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;header: &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;cell_style: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;borders: &lt;/span&gt;&lt;span class="sx"&gt;%i[top bottom left right]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;padding: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;inline_format: &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;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello_world.pdf'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7ipebpsonsdjm9zpm69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7ipebpsonsdjm9zpm69.png" alt="Rendered PDF file of the code below" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, now we’re enough stuffed to build something real !&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s build something together!
&lt;/h2&gt;

&lt;p&gt;Now that you know almost everything you need to know before getting started with PDF creation, let’s build.&lt;/p&gt;

&lt;p&gt;I propose we integrate a feature that I have already been asked for: &lt;strong&gt;export data from my database to a PDF file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine: We are an e-commerce shop, and we already display a page containing all the orders information to our administrators.&lt;/p&gt;

&lt;p&gt;We already expose an HTML view that reports the different orders 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;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&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;index&lt;/span&gt;
    &lt;span class="vi"&gt;@orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&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="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 you understand, HTML is old school; now we want to render PDFs.&lt;/p&gt;

&lt;p&gt;For this, we will use Prawn!&lt;/p&gt;

&lt;p&gt;In general, I like to create a fairly generic method in &lt;code&gt;ApplicationController&lt;/code&gt; in order to dynamically use it in my &lt;code&gt;OrdersController&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="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;Base&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="c1"&gt;# collection: ActiveRecord_Relation&lt;/span&gt;
    &lt;span class="c1"&gt;# column_names: Array of symbols or strings that represent the attributes I want to extract&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_pdf_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;column_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;font size='12'&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Prawn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&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;# The title of the document is the name of the model&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;humanize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;

    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;header: &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;cell_style: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="ss"&gt;borders: &lt;/span&gt;&lt;span class="sx"&gt;%i[top bottom left right]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;padding: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="ss"&gt;inline_format: &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;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move_down&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Rendered &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;align: :right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;size: &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;
    &lt;span class="n"&gt;pdf&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 render a PDF file, we will use &lt;code&gt;respond_to&lt;/code&gt; to respond based on the requested format:&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;OrdersController&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;index&lt;/span&gt;
    &lt;span class="vi"&gt;@orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&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;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_pdf_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;%i[id price username shipping_address]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;send_data&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;filename: &lt;/span&gt;&lt;span class="s1"&gt;'orders.pdf'&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;'application/pdf'&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 voilà 🎉&lt;/p&gt;

&lt;p&gt;When I access the URL &lt;a href="http://localhost:3000/orders.pdf" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:3000/orders.pdf&lt;/code&gt;&lt;/a&gt; on my server, a download starts with the file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffib3o5qcrykdnzeyxvvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffib3o5qcrykdnzeyxvvi.png" alt="Rendered PDF file of the code below" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Pros and Cons of Using Prawn
&lt;/h2&gt;

&lt;p&gt;Prawn is IMO the best tool to create PDF on the fly. I already used WickedPDF to generate PDF from HTML views, but it does not support well very complex views.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight and fast&lt;/strong&gt;: Prawn is designed to be performant and uses minimal resources. This makes it an excellent choice for applications where PDF generation needs to be quick and efficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible&lt;/strong&gt;: Prawn offers great flexibility for creating complex PDF documents with various graphic and textual elements. You can easily add text, images, tables, shapes, and even draw directly on the PDF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained&lt;/strong&gt;: Prawn requires no external dependencies, which simplifies its installation and usage. You can easily integrate it into your Ruby on Rails project!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich documentation&lt;/strong&gt;: &lt;a href="https://prawnpdf.org/docs/prawn/2.5.0/manual.pdf" rel="noopener noreferrer"&gt;Prawn has comprehensive documentation&lt;/a&gt;, making it easy to find solutions and learn best practices. The documentation is provided in PDF form… quite amusing.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DSL&lt;/strong&gt;: It is important to note that despite its simplicity, it requires a little adaptation time. Indeed, like all libraries that manipulate graphics (I think of TKinter or SDL), Prawn has its own rules. In my opinion, you need to spend some time on the documentation to really get the hang of the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited HTML integration&lt;/strong&gt;: Prawn is not a gem that converts HTML views to PDF. However, it is important to note that HTML tags are supported in a limited way. You have a good example of this in how I style my table headers. If you are looking to render HTML views in PDF, use WickedPDF.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;By following this guide, you have learned to generate PDFs on demand using the Prawn gem for your Ruby on Rails projects.&lt;/p&gt;

&lt;p&gt;Prawn stands out for its lightness, speed, and flexibility, making PDF generation both powerful and easy to integrate. Although it has some limitations, its advantages make it a solid option for most PDF generation needs.&lt;/p&gt;

&lt;p&gt;I encourage you to continue exploring the many possibilities offered by Prawn and to experiment with its various features to meet the needs of your projects. Feel free to share your own experiences and tips in the comments, I would love to read them!&lt;/p&gt;

&lt;p&gt;To make sure you don't miss any of my upcoming articles on Ruby and Rails, subscribe.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>9 things that 🚨Rubocop🚨 don’t want you to use</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Tue, 23 Jul 2024 07:00:00 +0000</pubDate>
      <link>https://forem.com/just-the-v/9-things-that-rubocop-dont-want-you-to-use-pb1</link>
      <guid>https://forem.com/just-the-v/9-things-that-rubocop-dont-want-you-to-use-pb1</guid>
      <description>&lt;p&gt;One day, I got lost in the Rubocop documentation. I was struck by a realization: there are many Ruby features I didn't know existed because Rubocop tells us not to use them.&lt;/p&gt;

&lt;p&gt;Today, I wanted to share with you the 9 discoveries that surprised me, which Rubocop recommends avoiding!&lt;/p&gt;

&lt;h3&gt;
  
  
  And / Or Operators (Style/AndOr)
&lt;/h3&gt;

&lt;p&gt;Did you know it is possible to write:&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;ary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[hello world]&lt;/span&gt;
&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'world'&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why use &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; when you can directly write &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The answer is quite simple, it's a matter of precedence compared to other operators.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ruby-doc.org/3.2.2/syntax/precedence_rdoc.html" rel="noopener noreferrer"&gt;The precedence table&lt;/a&gt; provided by the Ruby documentation shows us that the &lt;code&gt;or&lt;/code&gt; and &lt;code&gt;and&lt;/code&gt; operators are among the last to be evaluated in a Ruby expression.&lt;/p&gt;

&lt;p&gt;In practice, this means we can form quite strange expressions, as &lt;a href="https://rubystyle.guide/#and-or-flow" rel="noopener noreferrer"&gt;indicated in the RubyStyle guide&lt;/a&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="kp"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false (it's effectively (true or true) and false)&lt;/span&gt;
&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true (it's effectively true || (true &amp;amp;&amp;amp; false)&lt;/span&gt;
&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false (it's effectively (false or true) and false)&lt;/span&gt;
&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false (it's effectively false || (true &amp;amp;&amp;amp; false))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it is due to its rather misleading behavior that we favor using &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Then Keyword (Style/MultilineIfThen)
&lt;/h3&gt;

&lt;p&gt;There is a keyword &lt;code&gt;then&lt;/code&gt;, which is accepted by Rubocop for one-line &lt;code&gt;if-elsif&lt;/code&gt; blocks.&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&gt;"Low"&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&gt;"Average"&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="s2"&gt;"High"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is an interesting feature! It somewhat resembles a &lt;code&gt;case&lt;/code&gt; block but allows chaining different conditions easily.&lt;/p&gt;

&lt;p&gt;Rubocop's rule recognizes that the following syntax is bad 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="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="s2"&gt;"Low"&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="s2"&gt;"Average"&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="s2"&gt;"High"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which makes sense since &lt;code&gt;then&lt;/code&gt; is completely unnecessary in this case.&lt;/p&gt;




&lt;h3&gt;
  
  
  BEGIN and END blocks (Style/BeginBlock)
&lt;/h3&gt;

&lt;p&gt;This was also a real discovery. By using &lt;code&gt;BEGIN&lt;/code&gt;, we can execute code when our Ruby script starts. &lt;code&gt;END&lt;/code&gt; does the same, but when our script finishes:&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;# test.rb&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pp&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="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="s1"&gt;'Goodbye World!'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="s2"&gt;"Running my script..."&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;
&lt;span class="s2"&gt;"Running a script"&lt;/span&gt;
&lt;span class="s2"&gt;"Goodbye World!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I admit that in my daily life, I don't really think about using this syntax. I imagine it can be useful when designing CLI applications. To prepare and clean up a program.&lt;/p&gt;




&lt;h3&gt;
  
  
  ?c → 'c' (Style/CharacterLiteral)
&lt;/h3&gt;

&lt;p&gt;This one is very short. When you type &lt;code&gt;?&lt;/code&gt; followed by an ASCII character, it returns the ASCII character as a string:&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;irb&lt;/span&gt;
&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="sc"&gt;?a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;
&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="sc"&gt;?\t&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's the point? I don't really see either.&lt;/p&gt;

&lt;p&gt;In fact, this feature was implemented to allow obtaining the ASCII code of the character. Before Ruby 1.9, you could do:&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="mo"&gt;001&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="sc"&gt;?a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;
&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="sc"&gt;?\t&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But since Ruby 1.9, it only returns the character. The ASCII code can still be found via the &lt;code&gt;.ord&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;A good anecdote to tell at meetups!&lt;/p&gt;




&lt;h3&gt;
  
  
  There are a lot of aliases for collection methods (Style/CollectionMethods)
&lt;/h3&gt;

&lt;p&gt;We are used to using &lt;code&gt;.map&lt;/code&gt;, &lt;code&gt;.select&lt;/code&gt;, or &lt;code&gt;.include?&lt;/code&gt; when manipulating an Enumerable. But did you know there is an alias for these methods?&lt;/p&gt;

&lt;p&gt;Here is the list of methods with an alias that should be used (according to Rubocop):&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nf"&gt;collect!&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nf"&gt;collect_concat&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="nf"&gt;detect&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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="nf"&gt;member?&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nf"&gt;lenght&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Cop exists for one reason only: to ensure the use of a single method name throughout the project. In reality, I understand that it is impractical to have a project containing both &lt;code&gt;.collect&lt;/code&gt; and &lt;code&gt;.map&lt;/code&gt;. It can require a mental effort to remember that they are the same thing.&lt;/p&gt;

&lt;p&gt;We have another justification for this choice in &lt;a href="https://rubystyle.guide/#map-find-select-reduce-include-size" rel="noopener noreferrer"&gt;the RubyStyle documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;“The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use of &lt;code&gt;select&lt;/code&gt; is encouraged over &lt;code&gt;find_all&lt;/code&gt; is that it goes together nicely with &lt;code&gt;reject&lt;/code&gt; and its name is pretty self-explanatory.”&lt;/p&gt;




&lt;h3&gt;
  
  
  % behaves like sprintf (Style/FormatString)
&lt;/h3&gt;

&lt;p&gt;I discovered that we can use &lt;code&gt;%&lt;/code&gt; like &lt;code&gt;sprintf&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="mo"&gt;001&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%5d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"   10"&lt;/span&gt;
&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"%5d"&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;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"   10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, it's a shortcut but does exactly the same thing as &lt;code&gt;sprintf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can pass arguments to it in the form of an Array if using multiple arguments during formatting:&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="mo"&gt;001&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="s2"&gt;"Name: %s, Age: %d"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"V"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Name: V, Age: 23"&lt;/span&gt;
&lt;span class="mo"&gt;002&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Name: %{name}, Age: %{age}"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"V"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;age: &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Name: V, Age: 23"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rubocop's rule is also there to ensure the use of a single method between &lt;code&gt;sprintf&lt;/code&gt;, &lt;code&gt;format&lt;/code&gt;, and &lt;code&gt;%&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  %w[] is not only a story of &lt;a href="https://dev.toStyle/PercentLiteralDelimiters"&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In Ruby, we are fortunate to have &lt;code&gt;%w[Hello World]&lt;/code&gt; notation to create an array of strings. But did you know it is possible to use almost any non-alphanumeric character as a delimiter?&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="sx"&gt;%w{Hello World}&lt;/span&gt;
&lt;span class="sx"&gt;%w/Hello World/&lt;/span&gt;
&lt;span class="sx"&gt;%w@Hello World@&lt;/span&gt;
&lt;span class="sx"&gt;%w-Hello World-&lt;/span&gt;
&lt;span class="sx"&gt;%w*Hello World*&lt;/span&gt;
&lt;span class="sx"&gt;%w&amp;amp;Hello World&amp;amp;&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;lt;&amp;gt; behaves like [] or ()&lt;/span&gt;
&lt;span class="sx"&gt;%w&amp;lt;Hello World&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So why do we use &lt;code&gt;[]&lt;/code&gt;? The answer is given in &lt;a href="https://rubystyle.guide/#percent-literal-braces" rel="noopener noreferrer"&gt;the RubyStyle doc&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;We most often use &lt;code&gt;[]&lt;/code&gt; because it aligns with the array syntax &lt;code&gt;[0, 1, 2]&lt;/code&gt;. It's as simple as that!&lt;/p&gt;

&lt;p&gt;But there are a few small exceptions like &lt;code&gt;%q&lt;/code&gt; which uses only parentheses &lt;code&gt;()&lt;/code&gt;. But also regexes that use &lt;code&gt;%r{}&lt;/code&gt; because parentheses &lt;code&gt;()&lt;/code&gt; and square brackets &lt;code&gt;[]&lt;/code&gt; are frequently used in regexes.&lt;/p&gt;




&lt;h3&gt;
  
  
  FlipFlop Operator (Lint/FlipFlop)
&lt;/h3&gt;

&lt;p&gt;This is a rather interesting find! The FlipFlop Operator. Let's see an example right away, and I'll explain the details:&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;# test.rb&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
  random line 1
  start
  interesting line 1
  interesting line 2
  end
  random line 2
&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each_line&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;line&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'start'&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'end'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt;
&lt;span class="n"&gt;interesting&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;interesting&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;2&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 FlipFlop operator works in two phases, "flip" and "flop".&lt;/p&gt;

&lt;p&gt;The operator "flips" (becomes true) when the first condition is true, and "flops" (becomes false again) when the second condition is true. Between these two points, it remains true, even if the first condition becomes false.&lt;/p&gt;

&lt;p&gt;Honestly, I quite like this syntax.&lt;/p&gt;

&lt;p&gt;Rubocop advises against using the FlipFlop operator in Ruby because it complicates code readability, making its behavior opaque to those unfamiliar with the operator. It encourages the use of clearer and more explicit alternatives such as loops and conditions to improve code maintainability.&lt;/p&gt;




&lt;h3&gt;
  
  
  There are a lot of Perl-style global variables (Style/SpecificGlobalVars)
&lt;/h3&gt;

&lt;p&gt;I'm not going to list them all, but Ruby exposes many so-called “Perl-style” variables. You can find a &lt;a href="https://ruby.fandom.com/wiki/Special_variable" rel="noopener noreferrer"&gt;complete list here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, these are variables that start with &lt;code&gt;$&lt;/code&gt; and can be found in two forms: the short form, or the long form in English.&lt;/p&gt;

&lt;p&gt;Here's a small example of variables I liked:&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;# Print file name&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$0&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$PROGAM_NAME&lt;/span&gt;

&lt;span class="c1"&gt;# Print required files&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$:&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$LOAD_PATH&lt;/span&gt;

&lt;span class="c1"&gt;# Print error backtrace and error information&lt;/span&gt;
&lt;span class="k"&gt;begin&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;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'non_existant_file.txt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$ERROR_POSITION&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$@&lt;/span&gt;

  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&gt;$ERROR_INFO&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="vg"&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;There are many others, most of them having their equivalent in short/long versions.&lt;/p&gt;




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

&lt;p&gt;I am delighted to see the diversity and richness of Ruby's features we explored today, even if Rubocop sometimes advises us to avoid them.&lt;/p&gt;

&lt;p&gt;This variety demonstrates the flexibility and power of the language, offering us multiple ways to write and structure our code. Discovering these less common aspects can inspire us to experiment further and deepen our understanding of Ruby.&lt;/p&gt;

&lt;p&gt;I would be very curious to know what the latest Rubocop Cop that taught you about our favorite language.&lt;/p&gt;

&lt;p&gt;Feel free to subscribe so you don't miss my next breakdown on Ruby/Rails topic.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Everything you needed to know about respond_to 🔎</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Thu, 18 Jul 2024 07:06:49 +0000</pubDate>
      <link>https://forem.com/just-the-v/everything-you-needed-to-know-about-respondto-2511</link>
      <guid>https://forem.com/just-the-v/everything-you-needed-to-know-about-respondto-2511</guid>
      <description>&lt;p&gt;In Rails, creating a controller that returns an HTML view is standard. But did you know that it is possible to return HTML, CSV, JSON, PDF, XML, and many other formats using the &lt;code&gt;respond_to&lt;/code&gt; method in the same endpoint ?&lt;/p&gt;

&lt;p&gt;That's what we'll explore today!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What exactly is respond_to?&lt;/li&gt;
&lt;li&gt;You can deal with a LOT of formats in the same endpoint&lt;/li&gt;
&lt;li&gt;You can set default actions&lt;/li&gt;
&lt;li&gt;You can set up customised Mime Types&lt;/li&gt;
&lt;li&gt;You can mix variants and formats easily !&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  What exactly is &lt;code&gt;respond_to&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;To answer this question, let's look at a concrete example of using &lt;code&gt;respond_to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's start with this controller, &lt;code&gt;ArticlesController&lt;/code&gt;, which has only one method: &lt;code&gt;show&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to Rails' convention, we all know that the &lt;code&gt;show&lt;/code&gt; method will return an HTML view. This HTML view is templated via the file &lt;code&gt;app/views/articles/show.html.erb&lt;/code&gt;. All these rules are implicit because the default format is HTML.&lt;/p&gt;

&lt;p&gt;But imagine that tomorrow you want to be able to return JSON instead of HTML, without changing your logic.&lt;/p&gt;

&lt;p&gt;You would use &lt;code&gt;respond_to&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="c1"&gt;# will render show.html.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@article&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;From now on, you can pass a particular argument to your route: &lt;strong&gt;the format&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's see how how it works using the routes helpers :&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article_path&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="c1"&gt;# by default, format is HTML&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"article/1"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;article_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format: :json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"article/1.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, simply by adding the extension &lt;code&gt;.json&lt;/code&gt; at the end of your route, it will call for JSON format !&lt;/p&gt;

&lt;p&gt;Now that we have the basics, let's look at some advanced use cases for &lt;code&gt;respond_to&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  You can deal with a LOT of formats in the same endpoint
&lt;/h3&gt;

&lt;p&gt;As seen earlier, you can return JSON and HTML in the same method. But in fact, there are many other possible formats!&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;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="c1"&gt;# show.html.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# will automatically use #to_json&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xml&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;xml: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# will use #to_xml&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&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;csv: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&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;pdf: &lt;/span&gt;&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_pdf&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# let's assume that generate_pdf returns a pdf file&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 syntax, you can handle many formats. I have listed here those I have already used in production projects, but there are many others. As many as your application supports MimeTypes.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can set default actions
&lt;/h3&gt;

&lt;p&gt;Suppose you want to define a default action when the format is not supported. There is the use of &lt;code&gt;format.any&lt;/code&gt; that can save you!&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;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="c1"&gt;# show.html.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:not_acceptable&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;You can also specify in the &lt;code&gt;any&lt;/code&gt; argument the formats you want to consider in your default action:&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;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="c1"&gt;# show.html.erb&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&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;:xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:csv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:not_acceptable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important to note that if your route is called with an unsupported format, &lt;code&gt;respond_to&lt;/code&gt; will raise an &lt;code&gt;ActionController::UnknownFormat&lt;/code&gt; error.&lt;/p&gt;

&lt;h3&gt;
  
  
  You can set up customised Mime Types
&lt;/h3&gt;

&lt;p&gt;Have you set up your own Mime Type? &lt;code&gt;respond_to&lt;/code&gt; can still help you!&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/mime_types.rb&lt;/span&gt;
&lt;span class="no"&gt;Mime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="s2"&gt;"application/custom_mime_type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:custom&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;custom&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;plain: &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;What is good to note here is that the HTTP response will always have the correct &lt;code&gt;content-type&lt;/code&gt; header. In our case, the content-type will be &lt;code&gt;application/custom_mime_type&lt;/code&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  You can mix variants and formats easily !
&lt;/h3&gt;

&lt;p&gt;Variants are a very powerful feature in Rails. Suppose you want to do a very simple A/B 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticlesController&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;show&lt;/span&gt;
    &lt;span class="vi"&gt;@article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt; &lt;span class="c1"&gt;# always variant a or b&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&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;variant&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# show.html+a.erb&lt;/span&gt;
        &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;author&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# show.html+b.erb&lt;/span&gt;
        &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt; &lt;span class="p"&gt;{&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="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# but you can still catch if there is no variant&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;Here we will only return HTML. Depending on the variant declared in &lt;code&gt;request.variant&lt;/code&gt;, we can, for example, assign a value to &lt;code&gt;@author&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We always have access to &lt;code&gt;variant.none&lt;/code&gt; to be able to perform processing when no variant has been assigned to the request.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;respond_to&lt;/code&gt; method is a powerful tool, allowing you to respond to HTTP requests in various formats such as HTML, JSON, XML, CSV, and PDF. We have seen how &lt;code&gt;respond_to&lt;/code&gt; can improve the flexibility of Rails applications by handling multiple formats in the same endpoint and setting default actions for unsupported formats.&lt;/p&gt;

&lt;p&gt;I hope this article has made you appreciate the &lt;code&gt;respond_to&lt;/code&gt; method. Don't hesitate to subscribe so you don't miss my next breakdown of Ruby and Rails.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Binary Decision Tree in Ruby ? Say hello to Composite Pattern 🌳</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Mon, 01 Jul 2024 07:23:16 +0000</pubDate>
      <link>https://forem.com/wecasa/binary-decision-tree-in-ruby-say-hello-to-composite-pattern-h8n</link>
      <guid>https://forem.com/wecasa/binary-decision-tree-in-ruby-say-hello-to-composite-pattern-h8n</guid>
      <description>&lt;p&gt;Decision trees are very common algorithms in the world of development. On paper, they are quite simple. You chain if-else statements nested within each other. The problem arises when the tree starts to grow. If you're not careful, it becomes complex to read and, therefore, difficult to maintain.&lt;/p&gt;

&lt;p&gt;In this article, we'll see how we implemented decision trees at Wecasa to make them as readable and maintainable as possible.&lt;/p&gt;

&lt;p&gt;Before we go any further, let's take some time to define what a Composite is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Links Composite and Binary Decision Tree?
&lt;/h2&gt;

&lt;p&gt;Composite is a structural design pattern. It is a way of arranging objects in a tree structure, which allows us to have a logical hierarchy.&lt;/p&gt;

&lt;p&gt;When we talk about a decision tree, we can represent it as boxes that can contain boxes, which can contain boxes, which can contain… a final result.&lt;/p&gt;

&lt;p&gt;It's precisely this recursive aspect that makes the decision tree so powerful.&lt;br&gt;
Let's take an example. We want to create a decision tree to calculate the amount of the promotion a customer is eligible for on our e-commerce site.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In input, our tree receives a User object with all its attributes.&lt;/li&gt;
&lt;li&gt;In output, our tree returns a number.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Each node in our tree has a condition. You can notice by the color code that we have three types of Composites here: Creation Date, Zip Code, and whether the user has already made a purchase. Now that our example is set, let's see how this translates into code!&lt;/p&gt;


&lt;h2&gt;
  
  
  Firstly, we need tools !
&lt;/h2&gt;

&lt;p&gt;In our implementation of the decision tree, we will need three classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Node Class&lt;/strong&gt;: The base class for all other classes. Its purpose is to encapsulate the common logic for all different Nodes in my tree.
&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:left_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:right_node&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;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="c1"&gt;# this method is used to ease composites definition&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_condition&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="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;
      &lt;span class="n"&gt;left_node&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;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;right_node&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;user&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Leaf Class&lt;/strong&gt;: Its purpose is to end the tree. You can find them as bottom nodes without any child nodes. The only job they have to accomplish is to return the value they encapsulate.
&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Leaf&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:value&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;value&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;value&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;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;_&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Composite Classes&lt;/strong&gt;: Their role is to contain the condition to guide between the success branch and the failure branch, to proceed to the next step in the algorithm. They also contain a method to set arguments. We will see later why this method is useful.
&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreationDateComposite&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;
  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:days&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;left_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right_node&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;left_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left_node&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;right_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;right_node&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="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="n"&gt;call_with_condition&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="k"&gt;do&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;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt; &lt;span class="o"&gt;&amp;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;created_at&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;set_days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&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;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;days&lt;/span&gt;
    &lt;span class="nb"&gt;self&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;ZipCodeComposite&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HaveBoughtComposite&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I only show &lt;code&gt;CreationDateComposite&lt;/code&gt; so you understand the logic, no need to show the rest.&lt;/p&gt;


&lt;h2&gt;
  
  
  How We Finally Manage to Create a Binary Decision Tree
&lt;/h2&gt;

&lt;p&gt;Thanks to all the classes we have built, we will now create our first complete tree. Here is the proposed 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;PromotionTree&lt;/span&gt;
  &lt;span class="c1"&gt;# We use Singleton because we don't need to rebuild the tree&lt;/span&gt;
  &lt;span class="c1"&gt;# everytime we call the class&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Singleton&lt;/span&gt;

  &lt;span class="no"&gt;TREE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CreationDateComposite&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;ZipCodeComposite&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;BoughtComposite&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;Leaf&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="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;Leaf&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="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;BoughtComposite&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;Leaf&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;Leaf&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="mi"&gt;100&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="nf"&gt;set_zip_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'75'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="no"&gt;BoughtComposite&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;Leaf&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="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;ZipCodeComposite&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;Leaf&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="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="no"&gt;ZipCodeComposite&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;Leaf&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="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="no"&gt;Leaf&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_zip_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'78'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set_zip_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'75'&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="nf"&gt;set_days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&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;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;TREE&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;user&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;And now we can simply use the tree :&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;last&lt;/span&gt;
&lt;span class="no"&gt;PromotionTree&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;call&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; 60 🎉&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That being explained, this implementation offers several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No if-else mixed in my business logic&lt;/strong&gt;: By encapsulating conditions in separate objects, we have a clear separation of responsibilities. Each condition is handled by a specific composite, making the code more readable, elegant and maintainable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralization of conditions in separate objects&lt;/strong&gt;: This allows easy reuse and modification of conditions without touching the main application logic. Conditions can be changed or extended by creating new composites or modifying existing ones thanks to OOP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ease of testing&lt;/strong&gt;: Each component of the decision tree can be tested in isolation, simplifying the unit testing process. Tests can focus on specific conditions without having to simulate the entire decision tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensibility&lt;/strong&gt;: Adding new conditions or modifying existing conditions is simple and straightforward. Just create new composite nodes and insert them into the existing decision tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's easy to build a new tree&lt;/strong&gt;: Let's say tomorrow I want to build a new kind of tree. I always have all my Nodes ready to be used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But apart from this, I can see some downsides :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The setup cost is High&lt;/strong&gt;: I think it's not that pertinent to use this if you setup this kind of architecture if you don't plan to create other trees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It uses recursion&lt;/strong&gt;: The limit of your tree will be the limit of your Stack Size. So if your tree is bigger than your allowed stack size, you won't be able to use it. Fortunately, this value is quite big : 10867 for my setup. But it can depend on your setup !&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need to get into the habit to read it quickly&lt;/strong&gt;: At first it's kinda disturbing. The fact that we need to read on the top the Composite, and the bottom the value it used is quite weird. But don't be afraid of this syntax, I will publish another article to show you how I implemented a DSL for Tree building based on this architecture.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Using Composite to implement decision trees allows us to structure our conditions in a clear and maintainable way. Rather than getting lost in a maze of nested if-else statements, we have a logical hierarchy of decisions, each node playing a well-defined role.&lt;br&gt;
Thank you for reading. I invite you to subscribe so you don't miss the next articles that will be released.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Kernel#times is slower than while ???</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Sat, 11 May 2024 23:35:29 +0000</pubDate>
      <link>https://forem.com/just-the-v/if-you-want-your-ruby-application-to-be-efficient-keep-it-updated-37k0</link>
      <guid>https://forem.com/just-the-v/if-you-want-your-ruby-application-to-be-efficient-keep-it-updated-37k0</guid>
      <description>&lt;p&gt;Recently, I came across an article titled &lt;a href="https://www.johnhawthorn.com/2024/ruby-might-be-faster-than-you-think/" rel="noopener noreferrer"&gt;“Ruby might be faster than you think”&lt;/a&gt; In this article, John Hawthorn revisits the code snippet from the &lt;a href="https://github.com/wouterken/crystalruby/commit/e7c595122c5899d77ca29363aa401681e903deb8" rel="noopener noreferrer"&gt;CrystalRuby README&lt;/a&gt; and optimizes it to show that Ruby is just as performant, if not more, than the alternative presented by CrystalRuby.&lt;/p&gt;

&lt;p&gt;I understood everything in the article except for one part. The author replaces the use of &lt;code&gt;Kernel#times&lt;/code&gt; with a &lt;code&gt;while&lt;/code&gt; loop, and this change &lt;strong&gt;almost halved the code execution time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Seriously??? &lt;strong&gt;Halved&lt;/strong&gt;? If it was that simple and dumb, why am I not putting &lt;code&gt;while&lt;/code&gt; loops everywhere now?&lt;/p&gt;

&lt;p&gt;So, I quickly replicated his Benchmark to check the discrepancy, and more importantly, to see how significant it is.&lt;/p&gt;

&lt;p&gt;I run this benchmark:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fib_while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;a&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;fib_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&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="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&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;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'while loop'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fib_while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'times loop'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fib_times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare!&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 to my surprise, I got this output:&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rbenv&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;

&lt;span class="no"&gt;Warming&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;--------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;88.145&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;88.970&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;Calculating&lt;/span&gt; &lt;span class="o"&gt;-------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;871.169&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;      &lt;span class="mf"&gt;4.407&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.059392&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;885.111&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;      &lt;span class="mf"&gt;4.448&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.026107&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="no"&gt;Comparison&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="ss"&gt;loop:   &lt;/span&gt;&lt;span class="mf"&gt;885110.9&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ss"&gt;loop:   &lt;/span&gt;&lt;span class="mf"&gt;871169.3&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;slower&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m devastated. We’ve been lied to. I test several combinations of the Benchmark, but nothing changes. Locally, both methods are completely equal in terms of performance.&lt;/p&gt;

&lt;p&gt;Well, fortunately, I finally understood.&lt;/p&gt;

&lt;p&gt;The problem was with my Ruby version. I was running my Benchmark on Ruby version 3.2.0. Here’s the output once the same script was run on 3.3.0:&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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rbenv&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="mf"&gt;3.3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;

&lt;span class="no"&gt;Warming&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;--------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;   &lt;span class="mf"&gt;212.807&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;   &lt;span class="mf"&gt;109.784&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;Calculating&lt;/span&gt; &lt;span class="o"&gt;-------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;      &lt;span class="mf"&gt;2.133&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;     &lt;span class="mf"&gt;10.853&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.087899&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;      &lt;span class="mf"&gt;1.103&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;      &lt;span class="mf"&gt;5.599&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.080973&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="no"&gt;Comparison&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ss"&gt;loop:  &lt;/span&gt;&lt;span class="mf"&gt;2133283.8&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="ss"&gt;loop:  &lt;/span&gt;&lt;span class="mf"&gt;1103244.8&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.93&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;slower&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Wow. That’s a significant difference! So all this time, John Hawthorn was running his benchmark on Ruby version 3.3.0. He could have told us!&lt;/p&gt;

&lt;p&gt;Proud of this discovery, it led me to a question. How does the impact of versions really affect performance? And especially, what is the state for versions prior to 3.2.0?&lt;/p&gt;

&lt;p&gt;I then take the oldest version I have locally on my computer and test 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="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;rbenv&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ruby&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;

&lt;span class="no"&gt;Warming&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;--------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;26.937&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;20.069&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="no"&gt;Calculating&lt;/span&gt; &lt;span class="o"&gt;-------------------------------------&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;269.869&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;      &lt;span class="mf"&gt;1.374&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.090573&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="kp"&gt;loop&lt;/span&gt;    &lt;span class="mf"&gt;200.938&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;±&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;      &lt;span class="mf"&gt;1.024&lt;/span&gt;&lt;span class="no"&gt;M&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;   &lt;span class="mf"&gt;5.093709&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="no"&gt;Comparison&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ss"&gt;loop:   &lt;/span&gt;&lt;span class="mf"&gt;269869.1&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;
          &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="ss"&gt;loop:   &lt;/span&gt;&lt;span class="mf"&gt;200938.2&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.34&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;slower&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The performance gap between version 3.3.0 and 2.7.1 is astonishing.&lt;/p&gt;

&lt;p&gt;But then comes a question, why is it so much faster in 3.3.0 compared to 3.2.0?&lt;/p&gt;

&lt;p&gt;The answer: ✨YJIT Compiler✨&lt;/p&gt;

&lt;p&gt;Without going into details, the use of YJIT drastically increases the performance of our &lt;code&gt;while&lt;/code&gt; loop. From what I understand, &lt;code&gt;while&lt;/code&gt; loops are much more predictable in terms of execution path, allowing YJIT to be very efficient.&lt;/p&gt;

&lt;p&gt;In conclusion, if you want your Ruby application to be efficient, keep it updated!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>learning</category>
      <category>coding</category>
    </item>
    <item>
      <title>How I manage to optimize my Active Admin in 4 simple tricks</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Sun, 07 Jan 2024 14:13:14 +0000</pubDate>
      <link>https://forem.com/just-the-v/how-i-manage-to-optimize-my-active-admin-in-4-simple-tricks-aij</link>
      <guid>https://forem.com/just-the-v/how-i-manage-to-optimize-my-active-admin-in-4-simple-tricks-aij</guid>
      <description>&lt;p&gt;ActiveAdmin is a commonly used tool for creating admin interfaces in Ruby on Rails applications. It's incredibly useful for quickly setting up an admin interface focused solely on the data you want to display. However, we often encounter pages that make numerous SQL requests and take a long time to load.&lt;/p&gt;

&lt;p&gt;Today, we're going to look at three things I always do to optimize my ActiveAdmin views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt;  Data Set Presentation&lt;br&gt;
  Introduction to ActiveAdmin&lt;br&gt;
  Filters&lt;br&gt;
       1️⃣ Always Use Custom Filters&lt;br&gt;
       2️⃣ Preload Your Own Collection and Cache It&lt;br&gt;
  Index&lt;br&gt;
       3️⃣ Preload Data in Your Controller&lt;br&gt;
       4️⃣ Preload Data in Your View&lt;br&gt;
  Results&lt;br&gt;
       Server-Side Rendering:&lt;br&gt;
       Rack-mini-profiler:&lt;br&gt;
  Conclusion&lt;/p&gt;
&lt;h2&gt;
  
  
  Data Set Presentation
&lt;/h2&gt;

&lt;p&gt;To illustrate our examples, let's imagine that you need to integrate an admin interface for a temp agency.&lt;/p&gt;

&lt;p&gt;You will have 4 tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A table to store clients who pay to find temporary workers (Owner)&lt;/li&gt;
&lt;li&gt;A table to store all your available temporary workers (User)&lt;/li&gt;
&lt;li&gt;A table to store all the missions (Mission)&lt;/li&gt;
&lt;li&gt;A table to store all the mission shifts (MissionShift)
&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="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"users"&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="s2"&gt;"email"&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="s2"&gt;"first_name"&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="s2"&gt;"last_name"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"owner"&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="s2"&gt;"name"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"missions"&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="s2"&gt;"description"&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="s2"&gt;"title"&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"client_id"&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="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"mission_shifts"&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"mission_id"&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;date&lt;/span&gt; &lt;span class="s2"&gt;"day"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"begin_time"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"end_time"&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"user_id"&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 relationships are :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A User have many MissionShifts.&lt;/li&gt;
&lt;li&gt;A MissionShift belongs to a Mission.&lt;/li&gt;
&lt;li&gt;A Mission belongs to an Owner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That being explained, let’s dig into our use case !&lt;/p&gt;


&lt;h2&gt;
  
  
  Introduction to ActiveAdmin
&lt;/h2&gt;

&lt;p&gt;Our goal is to use ActiveAdmin to create our admin interface as quickly as possible. You want a page that displays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The names of the missions&lt;/li&gt;
&lt;li&gt;The client to whom the mission belongs&lt;/li&gt;
&lt;li&gt;How many MissionShifts are staffed with a User&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By reading a bit of the documentation, you discover ActiveAdmin's wonderful DSL.&lt;/p&gt;

&lt;p&gt;You quickly come up with 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="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;selectable_column&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:owner&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mission Shifts'&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;mission&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;mission_shifts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mission_shifts&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mission_shifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_id&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="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mission_shifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;actions&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;Which results in this:&lt;/p&gt;

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

&lt;p&gt;You're ready to conquer the world with this admin page!&lt;/p&gt;

&lt;p&gt;Yet, you should know that this page contains 3 sources of N+1 queries. To prove this, we'll use the “rack-mini-profiler” gem. This gem allows you to trace the server's path while rendering the page.&lt;/p&gt;

&lt;p&gt;Here's a screenshot of what rack-mini-profiler shows us:&lt;/p&gt;

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

&lt;p&gt;We have 148 SQL calls in the view 😲. That's huge!&lt;/p&gt;

&lt;p&gt;Looking at the server side, here's the information we get when loading the page:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Completed 200 OK in 741ms (Views: 455.5ms | ActiveRecord: 266.5ms | Allocations: 1035126)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The page takes a lot of time to load and allocates a huge amount of memory!&lt;/p&gt;

&lt;p&gt;The purpose of this article is to show you how to significantly reduce these SQL calls and memory allocation, thereby optimizing the time it takes for the page to render and reducing memory leaks!&lt;/p&gt;




&lt;h2&gt;
  
  
  Filters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Always Use Custom Filters
&lt;/h3&gt;

&lt;p&gt;The first tip I'd like to share today is well-known. It doesn't solve our N+1 issue, but it greatly reduces the memory allocated by the page:&lt;/p&gt;

&lt;p&gt;Never leave the default filters on!&lt;/p&gt;

&lt;p&gt;In fact, ActiveAdmin tries to be helpful and generates filters for all attributes of your table.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you have many attributes, it's hard to navigate.&lt;/li&gt;
&lt;li&gt;If you have relationships with other models, ActiveAdmin will preload the entire ActiveRecord collection. In our case, we have the &lt;code&gt;Owner&lt;/code&gt; and &lt;code&gt;MissionShift&lt;/code&gt; models included by default.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Always specify at least one filter:&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;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the server-side rendering when I load the page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before the modification: &lt;code&gt;Completed 200 OK in 620ms (Views: 422.9ms | ActiveRecord: 186.0ms | Allocations: 928758)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;After the modification: &lt;code&gt;Completed 200 OK in 581ms (Views: 403.4ms | ActiveRecord: 168.0ms | Allocations: 703878)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a huge difference in memory allocation!&lt;/p&gt;

&lt;h3&gt;
  
  
  2️⃣ Preload Your Own Collection and Cache It
&lt;/h3&gt;

&lt;p&gt;If you still want a filter for a Model, write your own query to load the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="ss"&gt;:owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&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="ss"&gt;:id&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="o"&gt;...&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;Here's the server-side rendering when I load the page:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Completed 200 OK in 666ms (Views: 337.9ms | ActiveRecord: 316.7ms | Allocations: 761030)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's still better than with the default filters.&lt;/p&gt;

&lt;p&gt;If you want to go even further in optimization, you can also set up a caching logic 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;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="ss"&gt;:owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="nb"&gt;lambda&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;cache&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;'owners_name_id'&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;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&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;Owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&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="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Completed 200 OK in 816ms (Views: 531.6ms | ActiveRecord: 248.3ms | Allocations: 711373)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In terms of allocation, we've come a long way!&lt;/p&gt;




&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;p&gt;On our index page, we have two problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Displaying the &lt;code&gt;Owner&lt;/code&gt; column&lt;/li&gt;
&lt;li&gt;Displaying the number of MissionShifts that have a User per Mission&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To summarize how ActiveAdmin works, for each line, it makes 3 SQL queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One to find the Owner&lt;/li&gt;
&lt;li&gt;One to find the MissionShifts&lt;/li&gt;
&lt;li&gt;One to count the MissionShifts that have a User&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, all our N+1s are actually concentrated here.&lt;/p&gt;

&lt;p&gt;The only way to solve our issues is by preloading as much data as possible. For this, we have two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Preload the data in the controller&lt;/li&gt;
&lt;li&gt;Load the data once and memoize it in the view&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3️⃣ Preload Data in Your Controller
&lt;/h3&gt;

&lt;p&gt;ActiveAdmin allows us to create our own controller methods. So far, we have only played with the &lt;code&gt;#index&lt;/code&gt; method, so if we want to preload data, this is the place!&lt;/p&gt;

&lt;p&gt;Let's modify our file to preload the &lt;code&gt;Owner&lt;/code&gt; data directly from the 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="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Owner'&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;mission&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;owners&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;mission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;owner_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# before loading anything, we preload Owners&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
      &lt;span class="vi"&gt;@owners&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&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="nf"&gt;to_h&lt;/span&gt;
      &lt;span class="c1"&gt;# call for the initial index method&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;If we look at the rack-mini-profiler, we get this:&lt;/p&gt;

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

&lt;p&gt;We have reduced our database calls by ~30. Why this number? Well, as we have the same Owner multiple times on different Missions, ActiveAdmin kept the call in cache and pulled out the information rather than making a new request.&lt;/p&gt;

&lt;p&gt;This technique works well unless you have a large volume of data in the &lt;code&gt;Owner&lt;/code&gt; table. If you want to optimize a bit more, you can also use the cache. And if you want to optimize even further, you can reuse the cache we set up together in the filters to use the same values twice on the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  4️⃣ Preload Data in Your View
&lt;/h3&gt;

&lt;p&gt;Preloading data in the controller is good, but we might load too much. The Mission table has a larger volume than Owner, so we can't afford to preload the entire table. Unfortunately, we don't know which Missions will be loaded on the page at the controller level.&lt;/p&gt;

&lt;p&gt;But there is one place where we do know which Missions are displayed: in the view!&lt;/p&gt;

&lt;p&gt;In other words:&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;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Sample Column'&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;missions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 50&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;controller&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;index&lt;/span&gt;
      &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;missions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; NameError - undefined local variable or method `missions' for #&amp;lt;Admin::MissionsController:0x000000000a4128&amp;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;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 use this to our advantage!&lt;/p&gt;

&lt;p&gt;Our goal is to preload the number of MissionShifts with a User as well as the total number of MissionShifts per Mission.&lt;/p&gt;

&lt;p&gt;The first step is to find a query to preload this data for a set of Missions:&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;Mission&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="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# will output missions with "id", "total_shifts" and "shifts_with_user"&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;mission_shifts_with_users&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;left_joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:mission_shifts&lt;/span&gt;&lt;span class="p"&gt;)&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="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'COUNT(mission_shifts.id) AS total_shifts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'COUNT(DISTINCT CASE WHEN mission_shifts.user_id IS NOT NULL THEN mission_shifts.id END) AS shifts_with_user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'missions.id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to use this query in our view so that we only have to look up our mission in it and extract the information we need.&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;ActiveAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt; &lt;span class="no"&gt;Mission&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Mission Shifts'&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;mission&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@missions_with_mission_shifts&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;missions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mission_shifts_with_users&lt;/span&gt;
      &lt;span class="n"&gt;mission_shifts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@missions_with_mission_shifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;mission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mission_shifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shifts_with_user&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="n"&gt;mission_shifts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_shifts&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;By using memoization, we only make the query once, and it is usable for all our Missions.&lt;/p&gt;

&lt;p&gt;If we take a tour of rack-mini-profiler, we get:&lt;/p&gt;

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

&lt;p&gt;We have eliminated almost 100 database calls by preloading the data for Owners and MissionShifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Here are the results with all the tips applied:&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Side Rendering:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Before: &lt;code&gt;Completed 200 OK in 741ms (Views: 455.5ms | ActiveRecord: 266.5ms | Allocations: 1035126)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;After: &lt;code&gt;Completed 200 OK in 200ms (Views: 187.3ms | ActiveRecord: 5.9ms | Allocations: 517789)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory allocation was halved.&lt;/p&gt;

&lt;p&gt;The response time is around 200ms (which is acceptable).&lt;/p&gt;

&lt;h3&gt;
  
  
  Rack-mini-profiler:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Before:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;After:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The numbers speak for themselves, only 6 SQL queries compared to 148 before. It's a huge gap for the performance of your applications.&lt;/p&gt;

&lt;p&gt;The important indicator to look at is the &lt;code&gt;% in sql&lt;/code&gt;, which we reduced from 15.6% to 3.5%, a huge difference!&lt;/p&gt;

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

&lt;p&gt;In conclusion, this article demonstrated how to transform an initially heavy and inefficient ActiveAdmin interface into a significantly more effective and faster version. Thanks to four simple yet powerful tricks, we drastically reduced SQL queries, improved memory management, and optimized loading time. These improvements are not just technical gains; they translate into a smoother and more professional user experience. For Ruby on Rails developers using ActiveAdmin, these methods offer a concrete way to enhance the performance of their applications while maintaining the ease and speed of development that this gem offers.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What is the fastest way to remove data using Rails ?</title>
      <dc:creator>Just The V</dc:creator>
      <pubDate>Thu, 28 Dec 2023 20:13:46 +0000</pubDate>
      <link>https://forem.com/just-the-v/speed-vs-security-navigating-data-deletion-strategies-in-rails-1a1k</link>
      <guid>https://forem.com/just-the-v/speed-vs-security-navigating-data-deletion-strategies-in-rails-1a1k</guid>
      <description>&lt;p&gt;Efficient data record deletion is a crucial consideration for high-traffic Rails applications. In this article, we will explore various deletion methods using ActiveRecord.&lt;/p&gt;

&lt;p&gt;Our mission today is to delete an entire section of our database. Our work will impact 3 tables: Accounts, Missions, and Tasks.&lt;/p&gt;

&lt;p&gt;An Account has several Missions, and a Mission has several Tasks.&lt;/p&gt;

&lt;p&gt;Our goal is to delete all Accounts, Missions, and Tasks as quickly as possible!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;p&gt; 1. Dataset 💽&lt;br&gt;
 2. Deletion Methods 🗑️&lt;br&gt;
       1. #destroy_all&lt;br&gt;
       2. #in_batches(of: 1,000).destroy_all&lt;br&gt;
       3. #delete_all&lt;br&gt;
       4. #truncate&lt;br&gt;
       5. Add on_delete: :cascade on the foreign key&lt;br&gt;
 3. Benchmark 🥊&lt;br&gt;
       Interpretation of Results&lt;br&gt;
       Recommendations&lt;/p&gt;
&lt;h2&gt;
  
  
  Dataset 💽
&lt;/h2&gt;

&lt;p&gt;For today's benchmark, we will start with a classic scenario of a two-level has_many relationship.&lt;/p&gt;

&lt;p&gt;Here is the database schema we will be using:&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/schema.rb&lt;/span&gt;
&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"accounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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="s2"&gt;"first_name"&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="s2"&gt;"last_name"&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="s2"&gt;"phone"&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="s2"&gt;"email"&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="s2"&gt;"role"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"created_at"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"updated_at"&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;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_accounts_on_email"&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="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"missions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"account_id"&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;string&lt;/span&gt; &lt;span class="s2"&gt;"name"&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;date&lt;/span&gt; &lt;span class="s2"&gt;"due_date"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"created_at"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"updated_at"&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;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_missions_on_account_id"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="s2"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;force: :cascade&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;bigint&lt;/span&gt; &lt;span class="s2"&gt;"mission_id"&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;string&lt;/span&gt; &lt;span class="s2"&gt;"title"&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;text&lt;/span&gt; &lt;span class="s2"&gt;"description"&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="s2"&gt;"status"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"created_at"&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;datetime&lt;/span&gt; &lt;span class="s2"&gt;"updated_at"&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;index&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mission_id"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_tasks_on_mission_id"&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 see how the data is generated, you can access &lt;a href="https://github.com/just-the-v/fastest-way-to-insert-in-rails/blob/622bbb9accd919ca004d3fd6374642c5bdb042c4/lib/tasks/benchmark.rake" rel="noopener noreferrer"&gt;my repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In my Models, I default to the following configuration, with &lt;code&gt;dependent: :destroy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(We will discuss the different possibilities 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;# app/models/account.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&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;has_many&lt;/span&gt; &lt;span class="ss"&gt;:missions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/models/mission.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Mission&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dependent: :destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/models/task.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Task&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;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:mission&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deletion Methods 🗑️
&lt;/h2&gt;

&lt;p&gt;Let's look at all the methods we will compare today&lt;/p&gt;

&lt;h3&gt;
  
  
  1. #destroy_all
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;destroy_all&lt;/code&gt; method is a common approach for deleting records using ActiveRecord. It instantiates each record and calls &lt;code&gt;destroy&lt;/code&gt; individually, triggering associated callbacks. It's generally the slowest approach, but also the safest to use.&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;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since all Models have their &lt;code&gt;dependant: :destroy&lt;/code&gt; relationship, &lt;code&gt;destroy_all&lt;/code&gt; will trigger by callback the deletion of Missions associated with the Account we want to delete. But, as we delete a mission, we will also delete all the associated Tasks.&lt;/p&gt;

&lt;p&gt;It's hassle-free, but very slow!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. #in_batches(of: 1,000).destroy_all
&lt;/h3&gt;

&lt;p&gt;I see a lot of ideas that deleting records in batches increases performance. In fact, it can reduce memory load and improve execution 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="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;in_batches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;of: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mo"&gt;000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;destroy_all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. #delete_all
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;delete_all&lt;/code&gt; method deletes records from the database without going through ActiveRecord validation. So, we have no validation or callback. This means that data deletion will be refused at the database level, as integrity will be lost.&lt;/p&gt;

&lt;p&gt;Therefore, we must conscientiously order the deletion 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="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
&lt;span class="no"&gt;Mission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In theory, this method is much more efficient than any use of &lt;code&gt;destroy&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. #truncate
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;truncate&lt;/code&gt; method is a radical approach that deletes all table data in one operation.&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;Account&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;truncate_tables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'accounts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'missions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tasks'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are sure you want to delete all of your tables, this will always be the fastest way to delete your data. However, it is impossible to scope the data; it will really reset your table.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add on_delete: :cascade on the foreign key
&lt;/h3&gt;

&lt;p&gt;There is a way to delete your tables much more quickly by adding a cascade parameter to your foreign key.&lt;/p&gt;

&lt;p&gt;For this, we need a 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/20231227130700_add_delete_cascade.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddDeleteCascade&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;remove_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:missions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:accounts&lt;/span&gt;
    &lt;span class="n"&gt;add_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:missions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete: :cascade&lt;/span&gt;
    &lt;span class="n"&gt;remove_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:missions&lt;/span&gt;
    &lt;span class="n"&gt;add_foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:missions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete: :cascade&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 will transform the declaration of our foreign key in the schema 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;# db/schema.rb&lt;/span&gt;
&lt;span class="n"&gt;add_foreign_key&lt;/span&gt; &lt;span class="s2"&gt;"missions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"accounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete: :cascade&lt;/span&gt;
&lt;span class="n"&gt;add_foreign_key&lt;/span&gt; &lt;span class="s2"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"missions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete: :cascade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does this change?&lt;/p&gt;

&lt;p&gt;Now, when we delete an Account with &lt;code&gt;#delete&lt;/code&gt;, all records that depend on this Account will also be deleted. It's like a callback, but entirely managed by the database. This technique is very efficient! The only problem is that records deleted by the cascade are not logged.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark 🥊
&lt;/h2&gt;

&lt;p&gt;For the benchmark, we will delete the same set of data each time, which contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100,000 Accounts&lt;/li&gt;
&lt;li&gt;500,000 Missions (5 per Account)&lt;/li&gt;
&lt;li&gt;1,500,000 Tasks (3 per Mission)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All this makes us a nice total of about 2M records. Let's see together which method is the most efficient!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Average (s)&lt;/th&gt;
&lt;th&gt;Min (s)&lt;/th&gt;
&lt;th&gt;Max (s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#destroy_all&lt;/td&gt;
&lt;td&gt;412&lt;/td&gt;
&lt;td&gt;386&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#destroy_all with batches&lt;/td&gt;
&lt;td&gt;515&lt;/td&gt;
&lt;td&gt;492&lt;/td&gt;
&lt;td&gt;551&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#delete_all&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#delete_all on top of a database cascade&lt;/td&gt;
&lt;td&gt;4.7&lt;/td&gt;
&lt;td&gt;2.9&lt;/td&gt;
&lt;td&gt;7.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#truncate&lt;/td&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Well, the first thing to say is that clearly, #destroy_all and #destroy_all with batches are clearly behind the others!&lt;/p&gt;

&lt;p&gt;Here is the graphical representation of the 10 trials that were conducted for the 3 fastest methods:&lt;/p&gt;

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

&lt;p&gt;So here is the ranking, very predictably:&lt;/p&gt;

&lt;p&gt;🥇#truncate&lt;/p&gt;

&lt;p&gt;🥈#destroy_all on top of the cascade&lt;/p&gt;

&lt;p&gt;🥉#destroy_all&lt;/p&gt;

&lt;p&gt;truncate is therefore much more efficient than the other competitors. This result is not surprising, as, as mentioned earlier, truncate will delete the table all at once.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Interpretation of Results&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The benchmark results reveal significant differences between data deletion methods in a Rails application. Let's take a closer look at each method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;#destroy_all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With an average of 412 seconds, &lt;strong&gt;&lt;code&gt;#destroy_all&lt;/code&gt;&lt;/strong&gt; proves to be the slowest method. This slowness is due to the instantiation of each record and the execution of associated callbacks, which creates an additional burden.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;#destroy_all with batches&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Surprisingly, this method, with an average of 515 seconds, is even slower than the classic &lt;strong&gt;&lt;code&gt;#destroy_all&lt;/code&gt;&lt;/strong&gt;. Although it is supposed to be more efficient in theory thanks to batch management, the results indicate that batch processing can, in some cases, worsen performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;#delete_all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Significantly faster with an average of only 7 seconds, &lt;strong&gt;&lt;code&gt;#delete_all&lt;/code&gt;&lt;/strong&gt; avoids the costs associated with ActiveRecord callbacks and validations. However, it requires careful management of the deletion order to maintain data integrity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;#delete_all on top of a database cascade&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With an average of 4.7 seconds, this method proves even more effective. It takes advantage of cascade deletion triggers at the database level, offering a quick and direct way to manage dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;#truncate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By far the fastest method with an average of 1.8 seconds. As it resets tables without regard to individual records, &lt;strong&gt;&lt;code&gt;#truncate&lt;/code&gt;&lt;/strong&gt; offers unmatched performance, particularly suited to scenarios where the entirety of a table's data needs to be erased.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The graphical representation clearly confirms the superiority of &lt;strong&gt;&lt;code&gt;#truncate&lt;/code&gt;&lt;/strong&gt; in terms of speed, followed by methods based on the database cascade, and finally the &lt;strong&gt;&lt;code&gt;#destroy_all&lt;/code&gt;&lt;/strong&gt; approaches.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Recommendations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Considering the results, here are the recommendations for choosing the appropriate deletion method depending on specific needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For a secure and controlled deletion:&lt;/strong&gt; Use &lt;strong&gt;&lt;code&gt;#destroy_all&lt;/code&gt;&lt;/strong&gt;. Ideal when callbacks and validations are crucial for data integrity and business logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For total deletions without integrity constraints:&lt;/strong&gt; Prefer &lt;strong&gt;&lt;code&gt;#truncate&lt;/code&gt;&lt;/strong&gt;. This method is recommended in test scenarios or when you need to completely reset a table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For optimized performance with integrity constraints:&lt;/strong&gt; Consider &lt;strong&gt;&lt;code&gt;#delete_all&lt;/code&gt;&lt;/strong&gt; with a cascade parameter on foreign keys. This approach is suitable when data integrity is managed at the database level and ActiveRecord callbacks are not necessary.&lt;/li&gt;
&lt;li&gt;In any case: Avoid &lt;strong&gt;&lt;code&gt;#destroy_all with batches&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conclusion, data deletion in Rails applications requires a balance between efficiency, security, and data integrity. This benchmark reveals that each method has its distinct advantages: &lt;strong&gt;&lt;code&gt;#destroy_all&lt;/code&gt;&lt;/strong&gt; ensures secure deletion despite its slowness, &lt;strong&gt;&lt;code&gt;#delete_all&lt;/code&gt;&lt;/strong&gt; optimizes performance while managing data integrity at the database level, and &lt;strong&gt;&lt;code&gt;#truncate&lt;/code&gt;&lt;/strong&gt; stands out for its unmatched speed in complete resets.&lt;/p&gt;

&lt;p&gt;In the end, the choice of the appropriate method should be guided by the specific needs of your application and the technical compromises it implies. A deep understanding of these options is crucial to maintaining a balance between performance and reliability in managing your Rails application's data.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
