<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yashika Vijayvargiya</title>
    <description>The latest articles on DEV Community by Yashika Vijayvargiya (@yashika_vijayvargiya).</description>
    <link>https://dev.to/yashika_vijayvargiya</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3977303%2F1ec6531e-a0cf-4905-b074-574c5cf458f5.JPG</url>
      <title>DEV Community: Yashika Vijayvargiya</title>
      <link>https://dev.to/yashika_vijayvargiya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashika_vijayvargiya"/>
    <language>en</language>
    <item>
      <title>Your Rails Transaction Rolled Back. What Happened to the API Call, Email, or Job?</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Tue, 22 Sep 2026 10:00:38 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4</link>
      <guid>https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4</guid>
      <description>&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aa9h32U1f_A3BVBb3" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2Aa9h32U1f_A3BVBb3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A database transaction gives us a powerful guarantee:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Either the database changes succeed together, or they are rolled back.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But there is an important boundary that is easy to overlook.&lt;/p&gt;

&lt;p&gt;A database transaction can roll back &lt;strong&gt;database changes&lt;/strong&gt;. It cannot automatically roll back an HTTP request that has already reached another service, an email that has already been sent, or a background job that has already been enqueued.&lt;/p&gt;

&lt;p&gt;Consider 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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;"Yashika"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
 &lt;span class="no"&gt;WelcomeJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

 &lt;span class="k"&gt;raise&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;Rollback&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 User record is rolled back.&lt;/p&gt;

&lt;p&gt;But depending on when the job was enqueued, the background job may already exist in the queue.&lt;/p&gt;

&lt;p&gt;Now the job can execute with an ID for a record that no longer exists.&lt;/p&gt;

&lt;p&gt;The same class of problem can happen with external HTTP APIs and email delivery.&lt;/p&gt;

&lt;p&gt;This is the problem I wanted to make easier to detect in Rails applications, so I built &lt;strong&gt;TransactionGuard&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem: database transactions don’t cover external side effects
&lt;/h3&gt;

&lt;p&gt;A typical Rails transaction might look perfectly reasonable:&lt;br&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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; 
 &lt;span class="no"&gt;SomeExternalApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_user&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;WelcomeMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;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;At first glance, everything looks like one atomic operation.&lt;/p&gt;

&lt;p&gt;But it isn’t.&lt;/p&gt;

&lt;p&gt;There are actually multiple systems involved:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;┌──────────────────────┐&lt;br&gt;&lt;br&gt;
│ Rails Application │&lt;br&gt;&lt;br&gt;
└──────────┬───────────┘&lt;br&gt;&lt;br&gt;
│&lt;br&gt;&lt;br&gt;
ActiveRecord&lt;br&gt;&lt;br&gt;
transaction&lt;br&gt;&lt;br&gt;
│&lt;br&gt;&lt;br&gt;
┌────────────┴────────────┐&lt;br&gt;&lt;br&gt;
│ │&lt;br&gt;&lt;br&gt;
▼ ▼&lt;br&gt;&lt;br&gt;
Database changes External side effects&lt;br&gt;&lt;br&gt;
│&lt;br&gt;&lt;br&gt;
┌─────────────┼─────────────┐&lt;br&gt;&lt;br&gt;
│ │ │&lt;br&gt;&lt;br&gt;
▼ ▼ ▼&lt;br&gt;&lt;br&gt;
HTTP Email Job&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The database can participate in the transaction.&lt;/p&gt;

&lt;p&gt;The external systems generally cannot.&lt;/p&gt;

&lt;p&gt;So if the transaction fails:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Database&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;&lt;br&gt;
ROLLBACK&lt;/p&gt;

&lt;p&gt;HTTP request&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;&lt;br&gt;
Already sent&lt;/p&gt;

&lt;p&gt;Email&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;&lt;br&gt;
Already sent&lt;/p&gt;

&lt;p&gt;Background job&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;&lt;br&gt;
May already be queued&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s where subtle production bugs can appear.&lt;/p&gt;

&lt;p&gt;Imagine an application that creates a customer and then synchronizes that customer with an external CRM.&lt;br&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;Customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Customer&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;"Acme"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="no"&gt;CRMClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rollback&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the rollback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database: Customer does not exist
CRM: Customer may already exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the application and the external service disagree.&lt;/p&gt;

&lt;p&gt;This can become even harder to debug when the external operation is hidden inside a service object, callback, mailer, or job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why background jobs can be particularly tricky
&lt;/h3&gt;

&lt;p&gt;Consider:&lt;br&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;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
 &lt;span class="n"&gt;order&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;create!&lt;/span&gt; 
 &lt;span class="no"&gt;OrderConfirmationJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;raise&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;Rollback&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 database transaction rolls back the order.&lt;/p&gt;

&lt;p&gt;But the job enqueue is a separate operation.&lt;/p&gt;

&lt;p&gt;The job may later execute:&lt;br&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;OrderConfirmationJob&lt;/span&gt;&lt;span class="p"&gt;.&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;order&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and discover that the order doesn’t exist.&lt;/p&gt;

&lt;p&gt;TransactionGuard’s README demonstrates this exact class of problem: the database record can be rolled back while the job has already been enqueued.&lt;/p&gt;

&lt;p&gt;The problem isn’t that background jobs are bad.&lt;/p&gt;

&lt;p&gt;The problem is &lt;strong&gt;when they are triggered&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about emails?
&lt;/h3&gt;

&lt;p&gt;The same problem exists with email delivery.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
 &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt; 
 &lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
 &lt;span class="k"&gt;raise&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;Rollback&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 database operation can roll back after the email has already been delivered.&lt;/p&gt;

&lt;p&gt;You can end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database:
User creation rolled back

Email:
Welcome email already delivered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can create confusing user experiences and difficult-to-reproduce bugs.&lt;/p&gt;

&lt;p&gt;TransactionGuard detects both deliver_now and deliver_later when they occur inside an ActiveRecord transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  So I built TransactionGuard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TransactionGuard&lt;/strong&gt; is a Ruby/Rails gem that detects external side effects performed while an ActiveRecord transaction is open.&lt;/p&gt;

&lt;p&gt;The current version detects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP requests through Net::HTTP&lt;/li&gt;
&lt;li&gt;Email delivery through Action Mailer&lt;/li&gt;
&lt;li&gt;ActiveJob enqueueing&lt;/li&gt;
&lt;li&gt;ActiveJob immediate execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to automatically fix the code.&lt;/p&gt;

&lt;p&gt;The goal is to make the problem visible during development and testing.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashika279/transaction%5C_guard" rel="noopener noreferrer"&gt;https://github.com/yashika279/transaction_guard&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How TransactionGuard works
&lt;/h3&gt;

&lt;p&gt;The basic question is surprisingly simple:&lt;br&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;TransactionGuard&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an ActiveRecord transaction is currently open, TransactionGuard can identify it and let the relevant detector report the external operation.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;External operation
       │
       ▼
Is TransactionGuard enabled?
       │
       ▼
Is an ActiveRecord transaction open?
       │
       ├── No → Continue normally
       │
       └── Yes
             │
             ▼
       Report the operation
             │
             ▼
       warn / raise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the responsibility focused:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TransactionGuard detects the potentially unsafe operation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It doesn’t silently change your application’s behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting HTTP requests
&lt;/h3&gt;

&lt;p&gt;One of the first detectors I implemented was for Net::HTTP.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&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;TransactionGuard detects the request because it occurs while the transaction is open.&lt;/p&gt;

&lt;p&gt;The HTTP detector wraps common methods such as:GET POST PUT PATCH DELETE HEAD OPTIONS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, the detector checks whether a transaction is open before reporting the operation.&lt;/p&gt;

&lt;p&gt;There is also protection against reporting the same underlying request multiple times when one HTTP method eventually delegates to another internal method.&lt;/p&gt;

&lt;p&gt;That matters because instrumentation should provide useful signals without producing a wall of duplicate warnings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting email delivery
&lt;/h3&gt;

&lt;p&gt;The email detector covers:&lt;br&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;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;welcome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside a transaction.&lt;/p&gt;

&lt;p&gt;An important detail here is avoiding duplicate reporting.&lt;/p&gt;

&lt;p&gt;deliver_later internally involves ActiveJob, but from the application's perspective the operation is an &lt;strong&gt;email delivery&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;TransactionGuard therefore reports it as an email side effect instead of producing an additional warning for the internal ActiveJob enqueue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detecting ActiveJob operations
&lt;/h3&gt;

&lt;p&gt;TransactionGuard also hooks into ActiveJob operations.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;WelcomeJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside a transaction is reported as a job enqueue operation.&lt;/p&gt;

&lt;p&gt;It also detects:&lt;br&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;WelcomeJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a job execution operation.&lt;/p&gt;

&lt;p&gt;The current 0.1.0 implementation specifically targets ActiveJob APIs; direct Sidekiq, Resque, and similar non-ActiveJob APIs are outside the current detector scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three modes: warn, raise, and off
&lt;/h3&gt;

&lt;p&gt;I wanted the gem to be useful in different development workflows.&lt;/p&gt;

&lt;p&gt;TransactionGuard currently supports three modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;:warn&lt;/span&gt; &lt;span class="ss"&gt;:raise&lt;/span&gt; &lt;span class="ss"&gt;:off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default mode is:&lt;/p&gt;

&lt;h3&gt;
  
  
  Warn mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;TransactionGuard&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:warn&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 application continues running, but TransactionGuard reports the detected side effect.&lt;/p&gt;

&lt;p&gt;This is useful when introducing the gem into an existing application because you can discover problematic code without immediately breaking development or tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raise mode
&lt;/h3&gt;

&lt;p&gt;For stricter enforcement:&lt;br&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;TransactionGuard&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:raise&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an unsafe external side effect causes TransactionGuard to raise an error.&lt;/p&gt;

&lt;p&gt;This can be useful in tests when you want a transaction-side-effect violation to fail fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Off mode
&lt;/h3&gt;

&lt;p&gt;Detection can also be disabled:&lt;br&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;TransactionGuard&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;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:off&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 Rails integration currently defaults to :warn in development and test, and :off in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Add the gem to your Gemfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"transaction_guard"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;For local development, you can also point Rails to a local checkout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"transaction_guard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s2"&gt;"../transaction_guard"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is open source and available under the MIT license.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does the warning look like?
&lt;/h3&gt;

&lt;p&gt;Suppose we have:&lt;br&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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;

  &lt;span class="no"&gt;WelcomeJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TransactionGuard can report that a job enqueue happened while an ActiveRecord transaction was open.&lt;/p&gt;

&lt;p&gt;The important information is the operation and the caller location.&lt;/p&gt;

&lt;p&gt;That makes the warning actionable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction detected external side effect:
Job enqueue

Caller:
app/services/users/create.rb:...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of discovering the problem later through a production incident, you can catch it while developing or testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should we fix these problems?
&lt;/h3&gt;

&lt;p&gt;TransactionGuard intentionally doesn’t automatically move operations outside the transaction.&lt;/p&gt;

&lt;p&gt;That’s an application design decision.&lt;/p&gt;

&lt;p&gt;A common approach is to perform the external operation after the database transaction succeeds.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="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;create!&lt;/span&gt;

&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"active"&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;WelcomeJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails also provides mechanisms such as after_commit, which can be appropriate when an operation should happen only after a successful database commit.&lt;/p&gt;

&lt;p&gt;For more complex distributed workflows, patterns such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transactional outbox&lt;/li&gt;
&lt;li&gt;reliable event publishing&lt;/li&gt;
&lt;li&gt;post-commit job triggering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can be considered.&lt;/p&gt;

&lt;p&gt;The important idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database transaction
        │
        ▼
Commit succeeds
        │
        ▼
External side effect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database transaction
        │
        ├── Database change
        │
        └── External side effect
                │
                ▼
          Transaction rolls back
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TransactionGuard doesn’t decide which pattern your application should use. It tells you where the potentially unsafe boundary exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not just use after_commit everywhere?
&lt;/h3&gt;

&lt;p&gt;after_commit is useful, but it isn't a universal solution.&lt;/p&gt;

&lt;p&gt;Applications often have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service objects&lt;/li&gt;
&lt;li&gt;callbacks&lt;/li&gt;
&lt;li&gt;mailers&lt;/li&gt;
&lt;li&gt;background jobs&lt;/li&gt;
&lt;li&gt;API clients&lt;/li&gt;
&lt;li&gt;third-party integrations&lt;/li&gt;
&lt;li&gt;shared libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An external operation can be introduced far away from the transaction that eventually rolls back.&lt;/p&gt;

&lt;p&gt;The purpose of TransactionGuard is therefore not to replace Rails transaction patterns.&lt;/p&gt;

&lt;p&gt;It provides a &lt;strong&gt;development-time safety net&lt;/strong&gt; that helps identify these boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current limitations
&lt;/h3&gt;

&lt;p&gt;TransactionGuard is intentionally small in its first version.&lt;/p&gt;

&lt;p&gt;The current 0.1.0 implementation focuses on:&lt;br&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;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;
&lt;span class="no"&gt;Action&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;
&lt;span class="no"&gt;ActiveJob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Direct clients such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Faraday&lt;/span&gt;
&lt;span class="no"&gt;HTTParty&lt;/span&gt;
&lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are not directly hooked in the current version, although clients built on top of Net::HTTP may be detected. Similarly, direct Sidekiq and Resque APIs are not currently detected.&lt;/p&gt;

&lt;p&gt;These are areas that can evolve as the project grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the gem
&lt;/h3&gt;

&lt;p&gt;The project uses RSpec for its test suite and RuboCop for code quality.&lt;/p&gt;

&lt;p&gt;Run the tests with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run RuboCop with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rubocop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository also includes integration coverage around the detectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learned building it
&lt;/h3&gt;

&lt;p&gt;The interesting part of this project wasn’t just writing three detectors.&lt;/p&gt;

&lt;p&gt;It was thinking about &lt;strong&gt;where the boundary of a database transaction actually ends&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In Rails, 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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="c1"&gt;# ... end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can look like an atomic business operation.&lt;/p&gt;

&lt;p&gt;But if that block interacts with systems outside the database, the operation is no longer truly atomic.&lt;/p&gt;

&lt;p&gt;That distinction becomes especially important in applications that integrate with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment providers&lt;/li&gt;
&lt;li&gt;CRMs&lt;/li&gt;
&lt;li&gt;messaging platforms&lt;/li&gt;
&lt;li&gt;email providers&lt;/li&gt;
&lt;li&gt;analytics systems&lt;/li&gt;
&lt;li&gt;background job queues&lt;/li&gt;
&lt;li&gt;external APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rollback can undo your database state.&lt;/p&gt;

&lt;p&gt;It cannot necessarily undo what another system has already observed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bigger lesson
&lt;/h3&gt;

&lt;p&gt;A transaction is not the same thing as a distributed transaction.&lt;/p&gt;

&lt;p&gt;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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;
  &lt;span class="no"&gt;ExternalAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_user&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contains two different worlds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Database
           │
       transaction
           │
        rollback
           │
           X

       External API
           │
       request sent
           │
       cannot magically
       be rolled back
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you recognize that boundary, you can make a deliberate architectural decision about what should happen before commit, after commit, or through a more reliable event-driven workflow.&lt;/p&gt;

&lt;p&gt;That’s the problem TransactionGuard is designed to make visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try TransactionGuard
&lt;/h3&gt;

&lt;p&gt;If you’re working on a Rails application with external integrations, you can try the gem here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/yashika279/transaction%5C_guard" rel="noopener noreferrer"&gt;https://github.com/yashika279/transaction_guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d especially like feedback on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;additional external side effects worth detecting&lt;/li&gt;
&lt;li&gt;integrations with other HTTP clients&lt;/li&gt;
&lt;li&gt;background job adapters&lt;/li&gt;
&lt;li&gt;false positives&lt;/li&gt;
&lt;li&gt;better reporting&lt;/li&gt;
&lt;li&gt;Rails version compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve ever had a transaction roll back while an email, API call, or background job had already escaped the transaction, this is exactly the kind of problem TransactionGuard is trying to catch earlier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on September 22, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>opensource</category>
      <category>rails</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your AI Assistant Forgot You: Understanding Memory, Sessions, and OmniMemory</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 16 Sep 2026 11:00:34 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/your-ai-assistant-forgot-you-understanding-memory-sessions-and-omnimemory-40bc</link>
      <guid>https://dev.to/yashika_vijayvargiya/your-ai-assistant-forgot-you-understanding-memory-sessions-and-omnimemory-40bc</guid>
      <description>&lt;h3&gt;
  
  
  OmniMemory Explained: Giving AI Coding Agents Persistent, Git-Aware Memory
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;AI coding tools are evolving extremely quickly. OmniMemory is an actively developed open-source project, so commands, supported integrations, architecture, and behavior may change over time. This article is intended to create awareness and explain the ideas behind OmniMemory. Before installing or using it in a real project, always check the latest&lt;/em&gt; &lt;a href="https://github.com/SinghAbhinav04/Omni-Memory" rel="noopener noreferrer"&gt;&lt;em&gt;OmniMemory repository&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and documentation.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;AI coding assistants have become an important part of modern software development.&lt;/p&gt;

&lt;p&gt;Tools such as Claude Code, Cursor, OpenCode, Windsurf, and other AI-powered development environments can understand your repository, modify files, run commands, write tests, and help you debug complicated problems.&lt;/p&gt;

&lt;p&gt;But there is one problem that almost every AI coding workflow eventually runs into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI forgets.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may spend two hours explaining your architecture to an AI agent.&lt;/p&gt;

&lt;p&gt;You might explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why a particular service exists&lt;/li&gt;
&lt;li&gt;why a database query is written in a strange way&lt;/li&gt;
&lt;li&gt;why a certain method must not be changed&lt;/li&gt;
&lt;li&gt;why a particular workaround was introduced&lt;/li&gt;
&lt;li&gt;how two services communicate&lt;/li&gt;
&lt;li&gt;why a particular branch is implementing a specific approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the session ends.&lt;/p&gt;

&lt;p&gt;Later, you start another session and the AI doesn’t necessarily remember those decisions.&lt;/p&gt;

&lt;p&gt;You explain everything again.&lt;/p&gt;

&lt;p&gt;And sometimes, instead of asking, the AI simply guesses.&lt;/p&gt;

&lt;p&gt;This is the problem &lt;strong&gt;OmniMemory&lt;/strong&gt; is designed to address.&lt;/p&gt;

&lt;p&gt;OmniMemory describes itself as a &lt;strong&gt;memory and context layer for coding agents&lt;/strong&gt; that is persistent, branch-aware, Git-anchored, and local.&lt;/p&gt;

&lt;p&gt;But what does that actually mean?&lt;/p&gt;

&lt;p&gt;Let’s break it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: AI Coding Agents Have Short-Term Context
&lt;/h3&gt;

&lt;p&gt;Before understanding OmniMemory, let’s understand the problem it is trying to solve.&lt;/p&gt;

&lt;p&gt;Imagine you are working on a Rails application.&lt;/p&gt;

&lt;p&gt;Your project contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── models/
├── services/
├── controllers/
├── jobs/
└── policies/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You ask your AI coding agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Why are we using this service instead of calling Stripe directly from the controller?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You explain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We originally had the Stripe call inside the controller, but webhook retries caused duplicate records. We moved it into this service because the service handles idempotency.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI now understands the reason.&lt;/p&gt;

&lt;p&gt;You continue working.&lt;/p&gt;

&lt;p&gt;You make several changes.&lt;/p&gt;

&lt;p&gt;Then the session ends.&lt;/p&gt;

&lt;p&gt;The next day you start another conversation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Update the Stripe integration.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI may inspect the code, but the &lt;strong&gt;reason behind the architecture&lt;/strong&gt; might not be obvious from the code itself.&lt;/p&gt;

&lt;p&gt;This is an important distinction:&lt;/p&gt;

&lt;h3&gt;
  
  
  Code tells you WHAT.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Documentation may tell you HOW.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Memory can tell you WHY.
&lt;/h3&gt;

&lt;p&gt;That “why” is often the most valuable information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Markdown Files Are Not Always Enough
&lt;/h3&gt;

&lt;p&gt;A common solution is to create a file such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MEMORY.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and manually write:&lt;br&gt;
&lt;/p&gt;

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

- Stripe calls must go through PaymentService.
- Never call Stripe directly from controllers.
- Orders use pessimistic locking.
- Redis is used for distributed locks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works.&lt;/p&gt;

&lt;p&gt;But it creates another problem.&lt;/p&gt;

&lt;p&gt;The AI has to receive the entire document or search through it.&lt;/p&gt;

&lt;p&gt;If the file becomes large, injecting everything into every prompt can increase context size and token usage.&lt;/p&gt;

&lt;p&gt;You might end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt
  ↓
Entire MEMORY.md
  ↓
AI
  ↓
Actual question
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s not ideal.&lt;/p&gt;

&lt;p&gt;OmniMemory takes a different approach.&lt;/p&gt;

&lt;p&gt;Instead of simply pushing all memory into every prompt, it maintains a local memory store and retrieves relevant information based on the current task. The project describes this as context-aware retrieval using a BM25F ranker, code-graph proximity, and citation feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is OmniMemory?
&lt;/h3&gt;

&lt;p&gt;At a high level:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OmniMemory is a persistent memory layer for AI coding agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is designed to remember things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;decisions&lt;/li&gt;
&lt;li&gt;facts&lt;/li&gt;
&lt;li&gt;request/data flows&lt;/li&gt;
&lt;li&gt;implementation gotchas&lt;/li&gt;
&lt;li&gt;project-specific knowledge&lt;/li&gt;
&lt;li&gt;architectural reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The memory is not simply a collection of notes.&lt;/p&gt;

&lt;p&gt;The project anchors memories to the codebase and Git history.&lt;/p&gt;

&lt;p&gt;That gives the memory additional context about whether the information is still relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Four Important Ideas Behind OmniMemory
&lt;/h3&gt;

&lt;p&gt;There are four concepts that are particularly important to understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Persistent Memory
&lt;/h3&gt;

&lt;p&gt;The memory survives across AI sessions.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session 1
   ↓
AI learns architecture
   ↓
Session ends
   ↓
Memory disappears
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session 1
   ↓
AI learns architecture
   ↓
Memory stored
   ↓
Session 2
   ↓
Relevant memory retrieved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fundamental idea behind the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Branch-Aware Memory
&lt;/h3&gt;

&lt;p&gt;This is one of the more interesting parts of OmniMemory.&lt;/p&gt;

&lt;p&gt;Software projects are rarely developed on only one branch.&lt;/p&gt;

&lt;p&gt;You might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main
 ├── feature/payment-refactor
 ├── feature/search
 └── bugfix/checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each branch may introduce different decisions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main
  |
  +--- feature/payment-refactor
  | |
  | +--- PaymentService changed
  |
  +--- feature/search
          |
          +--- Elasticsearch introduced
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A memory created while working on feature/search may not make sense when working on feature/payment-refactor.&lt;/p&gt;

&lt;p&gt;OmniMemory therefore scopes memory around Git branches and tracks branch information. The repository describes merged branch memories as rolling into the base branch.&lt;/p&gt;

&lt;p&gt;This is significantly more useful than treating the entire repository as one flat memory store.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Git-Anchored Memory
&lt;/h3&gt;

&lt;p&gt;This is another major concept.&lt;/p&gt;

&lt;p&gt;Imagine the AI remembers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The checkout calculation happens in&lt;/em&gt; &lt;em&gt;CheckoutService."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But six months later you completely rewrite the checkout architecture.&lt;/p&gt;

&lt;p&gt;If the AI blindly trusts the old memory, it can give you incorrect information.&lt;/p&gt;

&lt;p&gt;That’s dangerous.&lt;/p&gt;

&lt;p&gt;OmniMemory attempts to connect memory to the code it describes and check whether the relevant code has changed.&lt;/p&gt;

&lt;p&gt;Its current implementation uses a code graph and Git information to perform symbol-level staleness checks. When tree-sitter isn’t available, it can fall back to file-level checking.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
  |
  +--- Code symbol
          |
          +--- Git history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the relevant symbol changes, the memory can be identified as potentially stale.&lt;/p&gt;

&lt;p&gt;This is much more useful than simply storing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-08-01&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because time alone doesn’t tell you whether the information is still correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Local-First Architecture
&lt;/h3&gt;

&lt;p&gt;OmniMemory is designed to operate locally.&lt;/p&gt;

&lt;p&gt;The repository describes its core as using Python’s standard library and SQLite, with no API key required for the core functionality.&lt;/p&gt;

&lt;p&gt;The architecture is essentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Repository
      |
      ↓
OmniMemory
      |
      ├── SQLite
      ├── Git information
      ├── Code graph
      └── Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository also describes the system as local-first, with no cloud memory store required for its core operation.&lt;/p&gt;

&lt;p&gt;This can be particularly attractive for developers working with private repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  How OmniMemory Works
&lt;/h3&gt;

&lt;p&gt;The repository summarizes its architecture as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CAPTURE
   ↓
STORE
   ↓
RANK + INJECT + ENFORCE
   ↓
CHECK
   ↓
VISUALIZE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s understand each stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Capture
&lt;/h3&gt;

&lt;p&gt;OmniMemory captures information from coding sessions.&lt;/p&gt;

&lt;p&gt;The project describes deterministic harness events such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UserPromptSubmit
        ↓
     Inject
        ↓
   AI session
        ↓
   SessionEnd
        ↓
     Capture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because memory capture isn’t supposed to depend entirely on the AI remembering to save something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Store
&lt;/h3&gt;

&lt;p&gt;The captured information is stored locally.&lt;/p&gt;

&lt;p&gt;The repository uses SQLite for its core storage.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Session
    |
    ↓
Memory
    |
    ↓
SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each memory can contain contextual information about the project and branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Retrieve Relevant Memory
&lt;/h3&gt;

&lt;p&gt;This is where the system becomes more interesting.&lt;/p&gt;

&lt;p&gt;Suppose you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Why does this service use Redis?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don’t want every memory in your project.&lt;/p&gt;

&lt;p&gt;You want memories related to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Redis
Service
Current file
Current symbols
Architecture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OmniMemory uses a BM25F-based ranking system with weighting across symbols, files, and prose. It also considers code-graph proximity and whether the agent has previously cited a memory.&lt;/p&gt;

&lt;p&gt;So conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your question
      |
      ↓
Memory search
      |
      ├── keyword relevance
      ├── symbol relevance
      ├── file relevance
      ├── code proximity
      └── previous citations
      |
      ↓
Relevant memories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to provide the AI with &lt;strong&gt;the useful memory&lt;/strong&gt; , rather than everything the system knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  BM25F Instead of Embeddings
&lt;/h3&gt;

&lt;p&gt;This is worth highlighting.&lt;/p&gt;

&lt;p&gt;Many modern AI retrieval systems immediately use vector embeddings.&lt;/p&gt;

&lt;p&gt;OmniMemory’s README instead describes its retrieval system around &lt;strong&gt;BM25F&lt;/strong&gt; and explicitly notes that it doesn’t require embeddings or an API key for this retrieval.&lt;/p&gt;

&lt;p&gt;BM25 is a traditional information-retrieval technique based largely on lexical relevance.&lt;/p&gt;

&lt;p&gt;The simplified idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query:
"Redis locking checkout"

        ↓

Search memories

        ↓

Rank memories containing relevant
terms and fields

        ↓

Return the most relevant memories
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be particularly useful for developer knowledge because code contains many exact identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CheckoutService
RedisLock
PaymentService
Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exact symbol and file matching can be highly valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Inject Memory Into the AI Context
&lt;/h3&gt;

&lt;p&gt;Once relevant memory is found, OmniMemory can inject it into the AI’s context.&lt;/p&gt;

&lt;p&gt;The project describes a VERIFIED PROJECT MEMORY block and an enforcement mechanism where the agent is expected to cite memory it uses or acknowledge when information isn't available in memory.&lt;/p&gt;

&lt;p&gt;The conceptual flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer question
       ↓
OmniMemory searches
       ↓
Relevant memory
       ↓
AI receives memory
       ↓
AI answers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why “Enforced Memory” Matters
&lt;/h3&gt;

&lt;p&gt;Imagine the AI doesn’t know why a piece of code exists.&lt;/p&gt;

&lt;p&gt;A dangerous AI behavior is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I think this was added because…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a guess.&lt;/p&gt;

&lt;p&gt;OmniMemory’s approach attempts to distinguish between remembered information and information that isn’t available in its memory.&lt;/p&gt;

&lt;p&gt;This encourages a safer behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Known from memory
        ↓
Cite it

Not known
        ↓
Say it isn't in memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository specifically describes this as making the agent cite the memory it used or admit that something isn’t in memory rather than inventing it.&lt;/p&gt;

&lt;p&gt;That distinction is extremely important for AI-assisted software development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Check Whether Memory Is Stale
&lt;/h3&gt;

&lt;p&gt;Imagine we have:&lt;br&gt;
&lt;/p&gt;

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

"PaymentService handles Stripe payment creation."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we refactor the application.&lt;/p&gt;

&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;handles the payment instead.&lt;/p&gt;

&lt;p&gt;The old memory should no longer be trusted.&lt;/p&gt;

&lt;p&gt;OmniMemory’s check command can compare memory anchors against the current Git/code graph state and flag stale memories.&lt;/p&gt;

&lt;p&gt;The project describes this at the symbol level when the code graph is available.&lt;/p&gt;

&lt;p&gt;That’s more precise than simply saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The file changed, therefore the memory is invalid.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 6: Clean Up Bad Memory
&lt;/h3&gt;

&lt;p&gt;Memory can become dangerous if everything is stored forever.&lt;/p&gt;

&lt;p&gt;Imagine your AI remembers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"We use Redis for checkout locking."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you remove Redis completely.&lt;/p&gt;

&lt;p&gt;If that memory remains forever, future AI sessions may receive incorrect information.&lt;/p&gt;

&lt;p&gt;OmniMemory includes memory hygiene mechanisms intended to quarantine abandoned-branch and long-stale/uncited memories, while keeping hard deletion human-gated.&lt;/p&gt;

&lt;p&gt;This creates an important lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory created
      ↓
Memory used
      ↓
Memory verified
      ↓
Code changes
      ↓
Memory becomes stale
      ↓
Memory quarantined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Code Graph
&lt;/h3&gt;

&lt;p&gt;OmniMemory also builds a code graph.&lt;/p&gt;

&lt;p&gt;The repository currently describes tree-sitter support for Python, JavaScript, and TypeScript, with a standard-library ast fallback for Python.&lt;/p&gt;

&lt;p&gt;A simplified graph could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderController
       |
       ↓
OrderService
       |
       ↓
PaymentService
       |
       ↓
StripeClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine a memory is attached to:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If you’re currently editing:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;the graph can help determine that the PaymentService-related memory is nearby in the code relationship.&lt;/p&gt;

&lt;p&gt;This provides another signal for memory retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Local Dashboard
&lt;/h3&gt;

&lt;p&gt;OmniMemory also provides a local UI.&lt;/p&gt;

&lt;p&gt;The repository says omni-memory ui can be used to browse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;knowledge graph&lt;/li&gt;
&lt;li&gt;repository graph&lt;/li&gt;
&lt;li&gt;branch graph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of treating memory as a black box, you can inspect what the system has stored.&lt;/p&gt;

&lt;p&gt;That’s useful because developers should be able to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“What does my AI actually remember about this project?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;The repository currently recommends installing the CLI through pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;omni-memory-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude Code, the project also provides a plugin installation approach. The current README shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add SinghAbhinav04/Omni-Memory
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;omni-memory@singhabhinav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because these commands may change as the project develops, &lt;strong&gt;always check the repository before following installation instructions from this article.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Most Important Commands
&lt;/h3&gt;

&lt;p&gt;After installation, the project currently distinguishes between two commands that are easy to confuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory &lt;span class="nb"&gt;bind&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are not the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  build
&lt;/h3&gt;

&lt;p&gt;build creates the memory and documentation from your repository.&lt;/p&gt;

&lt;p&gt;The project describes it as capturing decisions, flows, and gotchas, building the code graph, and generating files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MEMORY.md
api-map.md
linkup.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  bind
&lt;/h3&gt;

&lt;p&gt;bind connects OmniMemory to your development environment.&lt;/p&gt;

&lt;p&gt;It installs session hooks and writes the cross-IDE AGENTS.md.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; create the initial memory itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Initial Workflow
&lt;/h3&gt;

&lt;p&gt;The repository currently suggests this general sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;omni-memory-agent

omni-memory build

omni-memory &lt;span class="nb"&gt;bind

&lt;/span&gt;omni-memory ui

omni-memory doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The roles are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;build
 ↓
Create memory + docs

bind
 ↓
Connect memory to IDE

ui
 ↓
Inspect memory and graphs

doctor
 ↓
Check setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjpb7loev35r5suk341g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgjpb7loev35r5suk341g.png" width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memory Graph&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayjur99nbqmhir4napsw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayjur99nbqmhir4napsw.png" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Does build Require an AI Model?
&lt;/h3&gt;

&lt;p&gt;There is an important nuance here.&lt;/p&gt;

&lt;p&gt;The project says build can use an agent/LLM to analyze the repository.&lt;/p&gt;

&lt;p&gt;You can run it inside an AI IDE, or configure a model key.&lt;/p&gt;

&lt;p&gt;The repository currently lists model-key support for Gemini, Anthropic, and OpenAI. Without an AI/model path, it can still build the code graph and heuristic documentation, but it won’t generate AI-written facts.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OmniMemory core
      |
      ├── Local memory
      ├── SQLite
      ├── Git
      └── Code graph

Optional AI analysis
      |
      ├── Gemini
      ├── Anthropic
      └── OpenAI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OmniMemory itself isn’t the language model.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Useful Commands
&lt;/h3&gt;

&lt;p&gt;The current CLI includes commands for several different workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory recall &lt;span class="s2"&gt;"payment retry logic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Searches memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory remember &lt;span class="s2"&gt;"PaymentService must be idempotent"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adds a memory manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory forget &amp;lt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removes or manages a memory entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Branches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory branches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allows you to inspect Git topology and branch-specific memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping Memory Fresh
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebuilds the knowledge/code graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checks memory anchors against the codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;omni-memory gc &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Previews memory cleanup.&lt;/p&gt;

&lt;p&gt;The current project also provides a usage command for inspecting the memory footprint per prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  What About Token Usage?
&lt;/h3&gt;

&lt;p&gt;This is an interesting question for developers using paid AI models.&lt;/p&gt;

&lt;p&gt;If you inject an enormous MEMORY.md into every prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt
+
50,000 tokens of project memory
+
Your actual question
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you are potentially wasting context.&lt;/p&gt;

&lt;p&gt;OmniMemory instead tries to retrieve only relevant memory.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 memories

        ↓

Relevant retrieval

        ↓

5 useful memories

        ↓

AI prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project’s retrieval and usage tooling are designed around keeping the relevant memory footprint under control.&lt;/p&gt;

&lt;p&gt;However, this should &lt;strong&gt;not&lt;/strong&gt; be interpreted as a guaranteed percentage reduction in your AI bill.&lt;/p&gt;

&lt;p&gt;Actual token consumption depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the AI model&lt;/li&gt;
&lt;li&gt;the coding agent&lt;/li&gt;
&lt;li&gt;prompt size&lt;/li&gt;
&lt;li&gt;repository size&lt;/li&gt;
&lt;li&gt;retrieved memory&lt;/li&gt;
&lt;li&gt;task complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OmniMemory vs a Simple MEMORY.md
&lt;/h3&gt;

&lt;p&gt;Let’s compare the two approaches.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyi2q5zegxq4whysp064g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyi2q5zegxq4whysp064g.png" width="500" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important point isn’t that Markdown is bad.&lt;/p&gt;

&lt;p&gt;Markdown is excellent for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;human documentation&lt;/li&gt;
&lt;li&gt;project conventions&lt;/li&gt;
&lt;li&gt;onboarding&lt;/li&gt;
&lt;li&gt;architecture notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OmniMemory is trying to solve a different problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;machine-oriented persistent context for AI coding agents.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  OmniMemory vs RAG
&lt;/h3&gt;

&lt;p&gt;At a high level, OmniMemory may remind you of Retrieval-Augmented Generation (RAG).&lt;/p&gt;

&lt;p&gt;Traditional RAG looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
   ↓
Chunking
   ↓
Embeddings
   ↓
Vector database
   ↓
Similarity search
   ↓
LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OmniMemory takes a different approach for coding environments.&lt;/p&gt;

&lt;p&gt;It emphasizes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
+
Git
+
Code symbols
+
Branch information
+
Lexical ranking
+
Code graph
+
Agent citations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Find text that looks similar.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Find project knowledge that is relevant to what I’m currently changing.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters for software repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Real-World Rails Example
&lt;/h3&gt;

&lt;p&gt;Imagine a Rails application with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order
Payment
Refund
Stripe
Sidekiq
Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have an architectural rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment processing must be idempotent.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the reason isn’t obvious from the code.&lt;/p&gt;

&lt;p&gt;The reason is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stripe webhook
      ↓
Retry
      ↓
Same payment event
      ↓
Potential duplicate payment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the team introduced an idempotency mechanism.&lt;/p&gt;

&lt;p&gt;Six months later, another developer asks an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Can I remove this Redis lock?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without project memory, the AI might see the lock as unnecessary complexity.&lt;/p&gt;

&lt;p&gt;With relevant project memory, the agent can retrieve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment processing requires idempotency
because Stripe webhook retries can produce
duplicate processing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the AI has architectural context.&lt;/p&gt;

&lt;p&gt;This is where persistent project memory becomes useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Another Example: Open-Source Contributions
&lt;/h3&gt;

&lt;p&gt;This is especially interesting for open-source projects.&lt;/p&gt;

&lt;p&gt;Imagine you contribute to a large Rails repository.&lt;/p&gt;

&lt;p&gt;During your first contribution, you learn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do not modify this service directly.
The event data is generated from YAML.
Run validate:all before submitting a PR.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal AI conversation might forget these details later.&lt;/p&gt;

&lt;p&gt;Persistent project memory can help preserve this project-specific knowledge across sessions.&lt;/p&gt;

&lt;p&gt;For developers contributing to large open-source repositories, this can be extremely useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who Should Consider Using OmniMemory?
&lt;/h3&gt;

&lt;p&gt;OmniMemory is particularly interesting if you:&lt;/p&gt;

&lt;h3&gt;
  
  
  Work on large repositories
&lt;/h3&gt;

&lt;p&gt;Large repositories have more architectural context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work with AI coding agents every day
&lt;/h3&gt;

&lt;p&gt;The more you rely on AI, the more useful persistent context can become.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently switch branches
&lt;/h3&gt;

&lt;p&gt;Branch-aware memory can prevent unrelated context from bleeding across branches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintain long-running projects
&lt;/h3&gt;

&lt;p&gt;Long-lived projects accumulate decisions and historical knowledge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work on open source
&lt;/h3&gt;

&lt;p&gt;Project-specific conventions can take significant time to learn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently start new AI sessions
&lt;/h3&gt;

&lt;p&gt;Persistent memory can reduce the need to repeatedly explain the same project context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who Probably Doesn’t Need It?
&lt;/h3&gt;

&lt;p&gt;You probably don’t need a sophisticated memory layer for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hello_world.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one-off script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;small coding exercise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your project is tiny and the AI can understand everything from the repository in seconds, additional memory infrastructure may provide little benefit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Potential Concerns
&lt;/h3&gt;

&lt;p&gt;No tool should be adopted simply because it sounds interesting.&lt;/p&gt;

&lt;p&gt;There are some things developers should consider.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Memory Can Be Wrong
&lt;/h3&gt;

&lt;p&gt;Persistent memory is only useful if it remains accurate.&lt;/p&gt;

&lt;p&gt;That’s why Git anchoring and stale-memory detection are important.&lt;/p&gt;

&lt;p&gt;But you should still review what the system remembers.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. AI-Generated Memory Needs Review
&lt;/h3&gt;

&lt;p&gt;If an AI creates a memory about your architecture, that memory can itself be wrong.&lt;/p&gt;

&lt;p&gt;A persistent wrong answer can be worse than no memory.&lt;/p&gt;

&lt;p&gt;Always treat AI-generated project knowledge as something to verify.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Project Is Actively Evolving
&lt;/h3&gt;

&lt;p&gt;The repository is under active development.&lt;/p&gt;

&lt;p&gt;Its current README lists several capabilities as implemented and others as roadmap items.&lt;/p&gt;

&lt;p&gt;Therefore, don’t treat this article as a frozen specification.&lt;/p&gt;

&lt;p&gt;The official repository is the source of truth for current behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bigger Idea: AI Needs Long-Term Project Context
&lt;/h3&gt;

&lt;p&gt;The interesting thing about OmniMemory isn’t just the CLI.&lt;/p&gt;

&lt;p&gt;The bigger idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;AI coding agents need a memory system that understands software projects, not just text.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A software project contains relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File
 ↓
Class
 ↓
Method
 ↓
Database
 ↓
Service
 ↓
External API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also contains history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Commit
 ↓
Refactor
 ↓
Decision
 ↓
New architecture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it contains branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main
 ↓
feature
 ↓
experiments
 ↓
merge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful coding memory system needs to understand these relationships.&lt;/p&gt;

&lt;p&gt;That is what makes OmniMemory interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  OmniMemory’s Architecture in One Diagram
&lt;/h3&gt;

&lt;p&gt;At a high level, you can think about it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────┐
                         │ Developer │
                         └─────────┬─────────┘
                                   │
                                   ▼
                         ┌───────────────────┐
                         │ AI Coding Agent │
                         └─────────┬─────────┘
                                   │
                         ┌─────────▼─────────┐
                         │ OmniMemory │
                         └─────────┬─────────┘
                                   │
             ┌─────────────────────┼─────────────────────┐
             │ │ │
             ▼ ▼ ▼
        ┌─────────┐ ┌──────────┐ ┌──────────┐
        │ SQLite │ │ Git │ │Code Graph│
        └─────────┘ └──────────┘ └──────────┘
             │ │ │
             └─────────────────────┼─────────────────────┘
                                   │
                                   ▼
                         ┌───────────────────┐
                         │ Relevant Memory │
                         └─────────┬─────────┘
                                   │
                                   ▼
                         ┌───────────────────┐
                         │ AI Context │
                         └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key concept to remember.&lt;/p&gt;

&lt;p&gt;If you ran these today:&lt;/p&gt;

&lt;p&gt;pip install omni-memory-agent&lt;/p&gt;

&lt;p&gt;omni-memory build omni-memory bind omni-memory ui omni-memory doctor&lt;/p&gt;

&lt;p&gt;then you should not need to run all of them again when your AI session ends or you open a new VS Code window.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5aoobixbgny2cvlwx3n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy5aoobixbgny2cvlwx3n.png" width="786" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should normally just open your project and start using your AI assistant.&lt;/p&gt;

&lt;p&gt;The important part is that you ran:&lt;/p&gt;

&lt;p&gt;omni-memory bind&lt;/p&gt;

&lt;p&gt;That is the step that connects the generated memory/context to your development environment.&lt;/p&gt;

&lt;p&gt;When should you run omni-memory build again?&lt;/p&gt;

&lt;p&gt;This is the one you’ll potentially use regularly.&lt;/p&gt;

&lt;p&gt;For example, today you have:&lt;/p&gt;

&lt;p&gt;Project ├── Rails app ├── locking implementation ├── tests └── architecture&lt;/p&gt;

&lt;p&gt;You work for a few days and add significant new things:&lt;/p&gt;

&lt;p&gt;Project ├── Rails app ├── locking implementation ├── tests ├── new payment system ├── new background-job architecture └── new API integration&lt;/p&gt;

&lt;p&gt;Then run:&lt;/p&gt;

&lt;p&gt;omni-memory build&lt;/p&gt;

&lt;p&gt;to update the memory/documentation/code graph based on the newer state of your repository.&lt;/p&gt;

&lt;p&gt;So think of it as:&lt;/p&gt;

&lt;p&gt;Initial setup ↓ pip install ↓ omni-memory build ↓ omni-memory bind ↓ Use your AI normally ↓ New session ↓ Use AI normally ↓ Major project changes? ↓ omni-memory build One thing I'd recommend&lt;/p&gt;

&lt;p&gt;After you’ve closed/reopened VS Code, run:&lt;/p&gt;

&lt;p&gt;omni-memory doctor&lt;/p&gt;

&lt;p&gt;once just to verify that everything is still connected.&lt;/p&gt;

&lt;p&gt;If it says the setup is healthy, you don’t need to rebuild or bind again.&lt;/p&gt;

&lt;p&gt;Also, omni-memory ui is not what makes the AI remember things; it’s primarily for browsing/inspecting the generated memory, docs, and code graph.&lt;/p&gt;

&lt;p&gt;If you want, I can also explain exactly what Omni-Memory is storing, where that memory lives, and how your AI actually gets that memory when you start a new chat/session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;AI coding assistants are becoming increasingly capable.&lt;/p&gt;

&lt;p&gt;But intelligence isn’t the only problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context is the problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An AI can be extremely capable and still make a bad decision if it doesn’t know why your project was designed the way it was.&lt;/p&gt;

&lt;p&gt;OmniMemory approaches this problem by combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;persistent memory&lt;/li&gt;
&lt;li&gt;Git provenance&lt;/li&gt;
&lt;li&gt;branch awareness&lt;/li&gt;
&lt;li&gt;code relationships&lt;/li&gt;
&lt;li&gt;relevance ranking&lt;/li&gt;
&lt;li&gt;stale-memory detection&lt;/li&gt;
&lt;li&gt;memory hygiene&lt;/li&gt;
&lt;li&gt;local storage&lt;/li&gt;
&lt;li&gt;AI-agent integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most interesting part is that it doesn’t simply try to give the AI &lt;strong&gt;more context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It tries to give the AI &lt;strong&gt;the right context at the right time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a much more interesting problem.&lt;/p&gt;

&lt;p&gt;And as AI coding agents become a larger part of everyday software development, persistent project memory could become an important part of the developer tooling ecosystem.&lt;/p&gt;

&lt;p&gt;If you use Claude Code, Cursor, OpenCode, Windsurf, or another AI coding workflow, OmniMemory is worth understanding — even if you don’t immediately adopt it.&lt;/p&gt;

&lt;p&gt;The project is actively evolving, so check the official repository for the latest installation instructions, supported integrations, commands, and roadmap before using it in your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/SinghAbhinav04/Omni-Memory" rel="noopener noreferrer"&gt;https://github.com/SinghAbhinav04/Omni-Memory&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>developertools</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Pessimistic Locking in Rails — Preventing Race Conditions in Production</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 16 Sep 2026 10:48:06 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/pessimistic-locking-in-rails-preventing-race-conditions-in-production-4hjc</link>
      <guid>https://dev.to/yashika_vijayvargiya/pessimistic-locking-in-rails-preventing-race-conditions-in-production-4hjc</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In the previous article, we learned why databases need locking and how PostgreSQL uses &lt;strong&gt;MVCC (Multi-Version Concurrency Control)&lt;/strong&gt; to allow multiple transactions to work simultaneously.&lt;/p&gt;

&lt;p&gt;However, MVCC alone cannot prevent every concurrency issue.&lt;/p&gt;

&lt;p&gt;Imagine an e-commerce application where there is only &lt;strong&gt;one product left in stock&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Two customers click &lt;strong&gt;“Buy Now”&lt;/strong&gt; at almost the same time.&lt;/p&gt;

&lt;p&gt;Without proper locking, both requests may purchase the same product.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Pessimistic Locking&lt;/strong&gt; becomes essential.&lt;/p&gt;

&lt;p&gt;In this article, we’ll understand how pessimistic locking works in PostgreSQL and how Rails makes it easy to use in production applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Pessimistic Locking?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pessimistic locking is a strategy where a transaction &lt;strong&gt;locks a row before modifying it&lt;/strong&gt; , preventing other transactions from changing that row until the lock is released.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I’m assuming someone else might modify this row, so I’m locking it first.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unlike optimistic locking, pessimistic locking assumes conflicts are likely and prevents them before they happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Do We Need Pessimistic Locking?
&lt;/h3&gt;

&lt;p&gt;Consider a banking application.&lt;/p&gt;

&lt;p&gt;Current balance:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Two requests arrive simultaneously.&lt;/p&gt;

&lt;p&gt;Transaction A:&lt;br&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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transaction B:&lt;br&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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without locking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Balance = $100&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;↓&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transaction A reads $100&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;↓&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transaction B reads $100&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;↓&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A saves $20&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;↓&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;B saves $50&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;↓&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Final Balance = $50 ❌&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$100 - $80 - $50 = -$30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actual:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This is called a &lt;strong&gt;Lost Update&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Solves This
&lt;/h3&gt;

&lt;p&gt;PostgreSQL provides&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a transaction executes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that row becomes locked.&lt;/p&gt;

&lt;p&gt;Any other transaction trying to update the same row must wait until the first transaction commits or rolls back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rails Pessimistic Locking
&lt;/h3&gt;

&lt;p&gt;Rails exposes PostgreSQL row locking through several APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. lock
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&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;lock&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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

  &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the important part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeline
&lt;/h3&gt;

&lt;p&gt;Without locking&lt;br&gt;
&lt;/p&gt;

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

Read balance=100

                      Read balance=100

Update balance=20

                      Update balance=50

Final = 50 ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With locking&lt;br&gt;
&lt;/p&gt;

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

Lock row

Read balance=100

Update balance=20

Commit

                      Wait...

                      Lock acquired

                      Read balance=20

                      Update balance=-30

Commit
Final = -30 ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. lock!
&lt;/h3&gt;

&lt;p&gt;If you already have the record:&lt;br&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;account&lt;/span&gt; &lt;span class="o"&gt;=&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;find&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="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock!&lt;/span&gt;

&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;

&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of querying again, Rails locks the existing record.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. with_lock
&lt;/h3&gt;

&lt;p&gt;This is the cleanest API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&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;find&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="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;

  &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starts a transaction&lt;/li&gt;
&lt;li&gt;locks the row&lt;/li&gt;
&lt;li&gt;executes the block&lt;/li&gt;
&lt;li&gt;commits&lt;/li&gt;
&lt;li&gt;releases the lock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Equivalent SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&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;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why with_lock is Preferred
&lt;/h3&gt;

&lt;p&gt;Instead of:&lt;br&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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&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;lock&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="nb"&gt;id&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;You simply 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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&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;Cleaner.&lt;/p&gt;

&lt;p&gt;Safer.&lt;/p&gt;

&lt;p&gt;More readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Locking Multiple Rows
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;category_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;products&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;product&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;product&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;price: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&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;All selected rows remain locked until the transaction completes.&lt;/p&gt;

&lt;h3&gt;
  
  
  NOWAIT
&lt;/h3&gt;

&lt;p&gt;Sometimes you don’t want to wait.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&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;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FOR UPDATE NOWAIT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;NOWAIT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another transaction already owns the lock:&lt;/p&gt;

&lt;p&gt;Instead of waiting,&lt;/p&gt;

&lt;p&gt;PostgreSQL immediately raises an error.&lt;/p&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment processing&lt;/li&gt;
&lt;li&gt;reservation systems&lt;/li&gt;
&lt;li&gt;inventory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SKIP LOCKED
&lt;/h3&gt;

&lt;p&gt;Another useful option.&lt;br&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;Job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FOR UPDATE SKIP LOCKED"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of waiting,&lt;/p&gt;

&lt;p&gt;locked rows are skipped.&lt;/p&gt;

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

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

Job 2 Free

Job 3 Locked

Job 4 Free
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker gets:&lt;br&gt;
&lt;/p&gt;

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

Job 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Very common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sidekiq alternatives&lt;/li&gt;
&lt;li&gt;queue systems&lt;/li&gt;
&lt;li&gt;job schedulers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real Production Example 1
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Inventory Reservation
&lt;/h3&gt;

&lt;p&gt;Product&lt;br&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;Inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Safe implementation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&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="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Out of stock"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zero?&lt;/span&gt;
  &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inventory&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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No two users can purchase the last item simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Production Example 2
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Hotel Booking
&lt;/h3&gt;

&lt;p&gt;Room:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Without locking:&lt;/p&gt;

&lt;p&gt;Two guests reserve the same room.&lt;/p&gt;

&lt;p&gt;With locking:&lt;/p&gt;

&lt;p&gt;Guest A locks the room.&lt;/p&gt;

&lt;p&gt;Guest B waits.&lt;/p&gt;

&lt;p&gt;Guest A confirms booking.&lt;/p&gt;

&lt;p&gt;Guest B now sees:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Production Example 3
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Wallet Transfer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock&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="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
  &lt;span class="n"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents inconsistent balances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lock Duration
&lt;/h3&gt;

&lt;p&gt;A common misconception:&lt;/p&gt;

&lt;p&gt;People think locks remain forever.&lt;/p&gt;

&lt;p&gt;Reality:&lt;br&gt;
&lt;/p&gt;

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

↓

Lock row

↓

Update

↓

COMMIT

↓

Lock released
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locks exist &lt;strong&gt;only during the transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Things to Avoid
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Long Transactions
&lt;/h3&gt;

&lt;p&gt;Bad&lt;br&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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock!&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;30&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row remains locked for 30 seconds.&lt;/p&gt;

&lt;p&gt;Other users wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  External API Calls Inside Transactions
&lt;/h3&gt;

&lt;p&gt;Avoid:&lt;br&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;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock!&lt;/span&gt;
  &lt;span class="no"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Network&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="n"&gt;increase&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead:&lt;br&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;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charge&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;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock!&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep transactions as short as possible.&lt;/p&gt;

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

&lt;p&gt;✅ Prevents race conditions&lt;/p&gt;

&lt;p&gt;✅ Prevents lost updates&lt;/p&gt;

&lt;p&gt;✅ Guarantees data consistency&lt;/p&gt;

&lt;p&gt;✅ Great for financial applications&lt;/p&gt;

&lt;p&gt;✅ Works well for inventory systems&lt;/p&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;p&gt;❌ Transactions may block each other&lt;/p&gt;

&lt;p&gt;❌ Can reduce throughput&lt;/p&gt;

&lt;p&gt;❌ Long-running transactions hurt performance&lt;/p&gt;

&lt;p&gt;❌ Deadlocks become possible&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Use Pessimistic Locking?
&lt;/h3&gt;

&lt;p&gt;Use it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing&lt;/li&gt;
&lt;li&gt;Wallet balance&lt;/li&gt;
&lt;li&gt;Inventory reservation&lt;/li&gt;
&lt;li&gt;Ticket booking&lt;/li&gt;
&lt;li&gt;Hotel reservation&lt;/li&gt;
&lt;li&gt;Airline seats&lt;/li&gt;
&lt;li&gt;Banking&lt;/li&gt;
&lt;li&gt;Subscription renewals&lt;/li&gt;
&lt;li&gt;Coupon redemption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple profile updates&lt;/li&gt;
&lt;li&gt;Blog posts&lt;/li&gt;
&lt;li&gt;User preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interview Questions
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Difference between lock, lock!, and with_lock
&lt;/h3&gt;

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

&lt;p&gt;Used while querying.&lt;br&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;lock&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;lock!&lt;/p&gt;

&lt;p&gt;Locks an already loaded record.&lt;br&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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lock!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Starts a transaction, locks the record, executes the block, and commits automatically.&lt;br&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;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What SQL does Rails generate?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What happens if another transaction tries to update the same row?
&lt;/h3&gt;

&lt;p&gt;It waits until the first transaction commits or rolls back.&lt;/p&gt;

&lt;h3&gt;
  
  
  When would you use SKIP LOCKED?
&lt;/h3&gt;

&lt;p&gt;When building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job queues&lt;/li&gt;
&lt;li&gt;Background workers&lt;/li&gt;
&lt;li&gt;Task schedulers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;where workers should continue processing available rows instead of waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pessimistic locking prevents concurrent modifications by locking rows before updates.&lt;/li&gt;
&lt;li&gt;Rails supports it through lock, lock!, and with_lock.&lt;/li&gt;
&lt;li&gt;PostgreSQL implements it using SELECT ... FOR UPDATE.&lt;/li&gt;
&lt;li&gt;Keep transactions short to minimize lock contention.&lt;/li&gt;
&lt;li&gt;Use features like NOWAIT and SKIP LOCKED when appropriate for advanced concurrency scenarios.&lt;/li&gt;
&lt;li&gt;Pessimistic locking is ideal when data correctness is more important than maximizing concurrency.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>locking</category>
      <category>optimisticlocking</category>
      <category>pessimisticlocking</category>
      <category>rails</category>
    </item>
    <item>
      <title>Monolith vs Microservices: Architecture, Trade-offs, and How to Choose</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 09 Sep 2026 09:11:17 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/monolith-vs-microservices-architecture-trade-offs-and-how-to-choose-2kdk</link>
      <guid>https://dev.to/yashika_vijayvargiya/monolith-vs-microservices-architecture-trade-offs-and-how-to-choose-2kdk</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;When building a new application, one of the first architectural decisions a team faces is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Should we build a monolith or use microservices?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question sounds simple, but the answer isn’t.&lt;/p&gt;

&lt;p&gt;Over the last decade, microservices have become one of the most discussed software architecture patterns. Many engineering teams moved from monolithic applications to microservices expecting better scalability, faster deployments, and greater flexibility.&lt;/p&gt;

&lt;p&gt;But microservices also introduce significant complexity.&lt;/p&gt;

&lt;p&gt;You now have multiple applications, multiple deployments, network communication, distributed failures, observability challenges, and often multiple databases.&lt;/p&gt;

&lt;p&gt;A monolith, on the other hand, can be much simpler to develop, test, deploy, and operate.&lt;/p&gt;

&lt;p&gt;So which one is better?&lt;/p&gt;

&lt;p&gt;The answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Neither is universally better. The right architecture depends on the application’s complexity, team structure, scalability requirements, and operational maturity.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we’ll explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What a monolithic architecture is&lt;/li&gt;
&lt;li&gt;What microservices are&lt;/li&gt;
&lt;li&gt;How the two architectures differ&lt;/li&gt;
&lt;li&gt;Advantages and disadvantages of each&lt;/li&gt;
&lt;li&gt;Database architecture&lt;/li&gt;
&lt;li&gt;Scaling&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Failure handling&lt;/li&gt;
&lt;li&gt;Team organization&lt;/li&gt;
&lt;li&gt;Modular monoliths&lt;/li&gt;
&lt;li&gt;When to choose each architecture&lt;/li&gt;
&lt;li&gt;How companies can migrate from a monolith to microservices&lt;/li&gt;
&lt;li&gt;Common microservices mistakes&lt;/li&gt;
&lt;li&gt;A practical decision framework&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Is Software Architecture?
&lt;/h3&gt;

&lt;p&gt;Before comparing monoliths and microservices, let’s understand what software architecture means.&lt;/p&gt;

&lt;p&gt;Software architecture describes how the major components of an application are organized and how they interact with each other.&lt;/p&gt;

&lt;p&gt;For example, an e-commerce application may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users
Products
Orders
Payments
Inventory
Notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architectural question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How should these components be organized and communicate with each other?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simple approach is to put everything into one application.&lt;/p&gt;

&lt;p&gt;That’s a monolith.&lt;/p&gt;

&lt;p&gt;Another approach is to split the application into multiple independently running services.&lt;/p&gt;

&lt;p&gt;That’s microservices.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Monolithic Architecture?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;monolithic application&lt;/strong&gt; is an application where most or all business functionality is developed, deployed, and operated as a single application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E-Commerce Application
                         |
        ---------------------------------------
        | | | | |
       Users Products Orders Payments Inventory
        | | | | |
        ---------------------------------------
                         |
                      Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application may have many modules internally, but it is deployed as one unit.&lt;/p&gt;

&lt;p&gt;For example, a Rails application might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── models/
│ ├── user.rb
│ ├── product.rb
│ ├── order.rb
│ └── payment.rb
│
├── controllers/
│ ├── users_controller.rb
│ ├── products_controller.rb
│ ├── orders_controller.rb
│ └── payments_controller.rb
│
└── services/
    ├── payment_service.rb
    └── order_service.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these components run inside the same application.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a Monolith Works
&lt;/h3&gt;

&lt;p&gt;Suppose a customer places an order.&lt;/p&gt;

&lt;p&gt;The request might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  v
Rails Application
  |
  +--&amp;gt; Validate User
  |
  +--&amp;gt; Check Inventory
  |
  +--&amp;gt; Create Order
  |
  +--&amp;gt; Process Payment
  |
  +--&amp;gt; Send Notification
  |
  v
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything happens within the same application boundary.&lt;/p&gt;

&lt;p&gt;The application can call another component directly:&lt;br&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;OrderService&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="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no network request between these components.&lt;/p&gt;

&lt;p&gt;This simplicity is one of the biggest advantages of a monolith.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of a Monolith
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. Simple Development
&lt;/h3&gt;

&lt;p&gt;Developers work in one repository.&lt;/p&gt;

&lt;p&gt;They don’t need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service discovery&lt;/li&gt;
&lt;li&gt;API gateways&lt;/li&gt;
&lt;li&gt;distributed tracing&lt;/li&gt;
&lt;li&gt;message brokers&lt;/li&gt;
&lt;li&gt;inter-service authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A developer can usually clone the repository and start working.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Simple Deployment
&lt;/h3&gt;

&lt;p&gt;You deploy one application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push
   |
   v
CI/CD
   |
   v
Deploy application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With microservices, you may need to deploy multiple services independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Simple Communication
&lt;/h3&gt;

&lt;p&gt;Inside a monolith:&lt;br&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;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With microservices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
      |
      | HTTP/gRPC/message
      v
Payment Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Network communication introduces latency and failure possibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Easier Transactions
&lt;/h3&gt;

&lt;p&gt;Suppose creating an order requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating an order&lt;/li&gt;
&lt;li&gt;Updating inventory&lt;/li&gt;
&lt;li&gt;Creating a payment record&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a monolith, these can potentially happen inside a single database transaction:&lt;br&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;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
  &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;
  &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&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 something fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order created
Inventory updated
Payment fails
       |
       v
Rollback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database can restore the previous state.&lt;/p&gt;

&lt;p&gt;Distributed transactions across microservices are significantly more complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Easier Debugging
&lt;/h3&gt;

&lt;p&gt;If something fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  ↓
Application
  ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can inspect one application’s logs and traces.&lt;/p&gt;

&lt;p&gt;With microservices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Gateway
   ↓
Order Service
   ↓
Inventory Service
   ↓
Payment Service
   ↓
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may need to inspect logs from several services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disadvantages of a Monolith
&lt;/h3&gt;

&lt;p&gt;Monoliths aren’t perfect.&lt;/p&gt;

&lt;p&gt;As applications grow, problems can appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Large Codebase
&lt;/h3&gt;

&lt;p&gt;A monolith can become difficult to understand.&lt;/p&gt;

&lt;p&gt;Over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
├── users
├── orders
├── payments
├── inventory
├── reporting
├── notifications
├── subscriptions
├── analytics
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything exists in the same repository.&lt;/p&gt;

&lt;p&gt;Without strong boundaries, modules can become tightly coupled.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Scaling the Entire Application
&lt;/h3&gt;

&lt;p&gt;Suppose only the image-processing functionality needs more CPU.&lt;/p&gt;

&lt;p&gt;With a traditional monolith:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     +--- Users
     +--- Orders
     +--- Payments
     +--- Images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may have to scale the entire application.&lt;/p&gt;

&lt;p&gt;You cannot independently scale only the image-processing component unless the architecture is designed for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deployment Coupling
&lt;/h3&gt;

&lt;p&gt;Suppose the payments team changes one small component.&lt;/p&gt;

&lt;p&gt;If the entire application is deployed together, that change may go through the same deployment pipeline as everything else.&lt;/p&gt;

&lt;p&gt;This can increase deployment risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Large Teams Can Become Difficult to Manage
&lt;/h3&gt;

&lt;p&gt;Imagine 100 developers working in one repository.&lt;/p&gt;

&lt;p&gt;You may encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;merge conflicts&lt;/li&gt;
&lt;li&gt;long CI pipelines&lt;/li&gt;
&lt;li&gt;unclear ownership&lt;/li&gt;
&lt;li&gt;tightly coupled modules&lt;/li&gt;
&lt;li&gt;coordination overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem isn’t necessarily that the application is a monolith.&lt;/p&gt;

&lt;p&gt;The problem may be that the organization and architecture haven’t evolved together.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Microservices Architecture?
&lt;/h3&gt;

&lt;p&gt;A microservices architecture divides an application into multiple independently deployable services.&lt;/p&gt;

&lt;p&gt;Each service focuses on a specific business capability.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Gateway
                         |
       -------------------------------------
       | | | |
       v v v v
    User Order Payment Inventory
   Service Service Service Service
       | | | |
       v v v v
    Database Database Database Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have its own codebase&lt;/li&gt;
&lt;li&gt;have its own deployment&lt;/li&gt;
&lt;li&gt;have its own database&lt;/li&gt;
&lt;li&gt;scale independently&lt;/li&gt;
&lt;li&gt;be owned by a specific team&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: E-Commerce Microservices
&lt;/h3&gt;

&lt;p&gt;Consider an e-commerce platform.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E-Commerce Monolith
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Service
Order Service
Product Service
Inventory Service
Payment Service
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service owns a specific responsibility.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  User Service
&lt;/h3&gt;

&lt;p&gt;Responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;registration&lt;/li&gt;
&lt;li&gt;login&lt;/li&gt;
&lt;li&gt;profiles&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Product Service
&lt;/h3&gt;

&lt;p&gt;Responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;products&lt;/li&gt;
&lt;li&gt;categories&lt;/li&gt;
&lt;li&gt;pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Order Service
&lt;/h3&gt;

&lt;p&gt;Responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;orders&lt;/li&gt;
&lt;li&gt;order lifecycle&lt;/li&gt;
&lt;li&gt;order history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Payment Service
&lt;/h3&gt;

&lt;p&gt;Responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment processing&lt;/li&gt;
&lt;li&gt;refunds&lt;/li&gt;
&lt;li&gt;payment status&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Notification Service
&lt;/h3&gt;

&lt;p&gt;Responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;emails&lt;/li&gt;
&lt;li&gt;SMS&lt;/li&gt;
&lt;li&gt;push notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Most Important Microservices Principle
&lt;/h3&gt;

&lt;p&gt;The goal is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Split the application into many small applications.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create independently deployable services around meaningful business boundaries.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This distinction is important.&lt;/p&gt;

&lt;p&gt;A system with 50 tiny services isn’t automatically better than a system with 5 well-designed services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Microservices
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. Independent Deployment
&lt;/h3&gt;

&lt;p&gt;A payment service can be deployed without deploying the order service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Service
      |
      v
Deploy independently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can enable teams to release features faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Independent Scaling
&lt;/h3&gt;

&lt;p&gt;Suppose the order service receives significantly more traffic than the user service.&lt;/p&gt;

&lt;p&gt;You can scale them independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Service
Instances: 3

Order Service
Instances: 20

Payment Service
Instances: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Team Ownership
&lt;/h3&gt;

&lt;p&gt;Different teams can own different services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Team A → User Service
Team B → Order Service
Team C → Payment Service
Team D → Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can reduce coordination between teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Technology Flexibility
&lt;/h3&gt;

&lt;p&gt;Different services can potentially use different technologies.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Service → Rails
Payment Service → Java
Recommendation → Python
Notification → Go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, just because you can use multiple languages doesn’t mean you should.&lt;/p&gt;

&lt;p&gt;Technology diversity introduces its own operational costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Fault Isolation
&lt;/h3&gt;

&lt;p&gt;Suppose the recommendation service fails.&lt;/p&gt;

&lt;p&gt;Ideally:&lt;br&gt;
&lt;/p&gt;

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

Order Service ✅
Payment Service ✅
User Service ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire application doesn’t necessarily have to go down.&lt;/p&gt;

&lt;p&gt;But this only works if services are designed with failure isolation in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disadvantages of Microservices
&lt;/h3&gt;

&lt;p&gt;This is where many teams underestimate the cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Network Communication
&lt;/h3&gt;

&lt;p&gt;Inside a monolith:&lt;br&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;payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With microservices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
      |
      | HTTP
      v
Payment Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The network can fail.&lt;/p&gt;

&lt;p&gt;Possible problems include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timeout&lt;/li&gt;
&lt;li&gt;connection failure&lt;/li&gt;
&lt;li&gt;DNS failure&lt;/li&gt;
&lt;li&gt;retry storms&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;duplicate requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Communication becomes a major architectural concern.&lt;/p&gt;

&lt;p&gt;We’ll explore this in detail in the next article.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Distributed Transactions
&lt;/h3&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     |
     +--&amp;gt; Inventory Service
     |
     +--&amp;gt; Payment Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens if:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order created ✅
Inventory updated ✅
Payment failed ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can’t simply rollback all services using a normal database transaction.&lt;/p&gt;

&lt;p&gt;This is one of the biggest differences between monolithic and distributed systems.&lt;/p&gt;

&lt;p&gt;Solutions may involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saga pattern&lt;/li&gt;
&lt;li&gt;compensating transactions&lt;/li&gt;
&lt;li&gt;event-driven architecture&lt;/li&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Debugging Becomes Harder
&lt;/h3&gt;

&lt;p&gt;A single user request might travel through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Gateway
    ↓
Order Service
    ↓
Inventory Service
    ↓
Payment Service
    ↓
Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the request fails, which service caused the problem?&lt;/p&gt;

&lt;p&gt;You need tools such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;centralized logging&lt;/li&gt;
&lt;li&gt;distributed tracing&lt;/li&gt;
&lt;li&gt;correlation IDs&lt;/li&gt;
&lt;li&gt;metrics&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Operational Complexity
&lt;/h3&gt;

&lt;p&gt;A monolith might require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
Database
Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A microservices system might require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 Services
10 Databases
Message Broker
API Gateway
Service Discovery
Centralized Logging
Distributed Tracing
Monitoring
Secrets Management
CI/CD pipelines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operational burden increases significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Data Consistency
&lt;/h3&gt;

&lt;p&gt;In a monolith, multiple modules can share a database.&lt;/p&gt;

&lt;p&gt;With microservices, each service ideally owns its data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     |
 Orders DB

Payment Service
     |
 Payments DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now querying data across services becomes more complicated.&lt;/p&gt;

&lt;p&gt;You can’t simply write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because the tables may exist in completely different databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Database vs Database per Service
&lt;/h3&gt;

&lt;p&gt;This is one of the most important architectural decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service --------\
Payment Service ------- &amp;gt; PostgreSQL
Inventory Service ----/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s easier initially.&lt;/p&gt;

&lt;p&gt;But services become coupled through the database.&lt;/p&gt;

&lt;p&gt;One service might directly modify another service’s tables.&lt;/p&gt;

&lt;p&gt;This weakens service boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database per Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     |
 Orders DB

Payment Service
     |
 Payments DB

Inventory Service
     |
 Inventory DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides stronger ownership.&lt;/p&gt;

&lt;p&gt;But now cross-service queries and transactions become harder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monolith vs Microservices: Database Comparison
&lt;/h3&gt;

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

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

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

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

&lt;p&gt;Usually shared&lt;/p&gt;

&lt;p&gt;Usually owned by service&lt;/p&gt;

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

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

&lt;p&gt;More complex&lt;/p&gt;

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

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

&lt;p&gt;Usually avoided across services&lt;/p&gt;

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

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

&lt;p&gt;Often eventual&lt;/p&gt;

&lt;p&gt;Data ownership&lt;/p&gt;

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

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

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

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

&lt;p&gt;Requires aggregation&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling Comparison
&lt;/h3&gt;

&lt;p&gt;Suppose your application receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 requests/second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A monolith can still scale.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load Balancer
      |
 -----------------
 | | |
App App App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing inherently wrong with scaling a monolith horizontally.&lt;/p&gt;

&lt;p&gt;Microservices simply allow more granular scaling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → 20 instances
User Service → 5 instances
Payment Service → 10 instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;High traffic does not automatically mean you need microservices.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A well-designed monolith can handle significant scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Comparison
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Monolith
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code
 ↓
Build
 ↓
Test
 ↓
Deploy
 ↓
Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usually one deployment pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     ↓
Pipeline
     ↓
Deploy

Payment Service
     ↓
Pipeline
     ↓
Deploy

Inventory Service
     ↓
Pipeline
     ↓
Deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You gain independent deployments but also need to manage multiple pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Comparison
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Monolith
&lt;/h3&gt;

&lt;p&gt;You can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit Tests
Integration Tests
System Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing is relatively straightforward because components are inside one application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;

&lt;p&gt;You need additional testing strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit Tests
     ↓
Service Tests
     ↓
Contract Tests
     ↓
Integration Tests
     ↓
End-to-End Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to verify not only that each service works but also that services understand each other’s contracts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure Handling
&lt;/h3&gt;

&lt;p&gt;Failure is much more visible in distributed systems.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service
     |
     v
Payment Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Payment service takes 10 seconds to respond.&lt;/p&gt;

&lt;p&gt;Should the order request wait?&lt;/p&gt;

&lt;p&gt;Probably not indefinitely.&lt;/p&gt;

&lt;p&gt;You may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;circuit breakers&lt;/li&gt;
&lt;li&gt;fallback behavior&lt;/li&gt;
&lt;li&gt;asynchronous processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Created
     |
     v
Publish Event
     |
     v
Payment Processing
     |
     v
Payment Completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows parts of the system to operate asynchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monolith vs Modular Monolith vs Microservices
&lt;/h3&gt;

&lt;p&gt;There is an important architecture between the two extremes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Modular Monolith&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A modular monolith is still deployed as one application, but its internal components have strict boundaries.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rails Application
                     |
       -----------------------------
       | | |
     Orders Payments Inventory
       | | |
       -----------------------------
                     |
                 Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application is deployed as one unit, but modules are isolated.&lt;/p&gt;

&lt;p&gt;This can provide many benefits of good architecture without immediately introducing distributed-system complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Modular Monoliths Are Important
&lt;/h3&gt;

&lt;p&gt;A common mistake is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monolith
   ↓
"We need microservices"
   ↓
20 services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better approach may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Monolith
   ↓
Identify boundaries
   ↓
Create modules
   ↓
Reduce coupling
   ↓
Measure actual bottlenecks
   ↓
Extract services only when necessary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the team time to understand the domain before introducing network boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Choose a Monolith?
&lt;/h3&gt;

&lt;p&gt;A monolith is often a good choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re building a new product&lt;/li&gt;
&lt;li&gt;The team is small&lt;/li&gt;
&lt;li&gt;The domain is still changing&lt;/li&gt;
&lt;li&gt;Requirements are uncertain&lt;/li&gt;
&lt;li&gt;You want fast iteration&lt;/li&gt;
&lt;li&gt;Operational resources are limited&lt;/li&gt;
&lt;li&gt;You don’t have strong service boundaries yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many startups, this is an excellent starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Consider Microservices?
&lt;/h3&gt;

&lt;p&gt;Microservices may make sense when:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Teams Need Independent Ownership
&lt;/h3&gt;

&lt;p&gt;If multiple teams need to work independently on different business domains, service boundaries can help.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Components Scale Differently
&lt;/h3&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Image Processing → Very high CPU
User Management → Low traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Independent scaling could be valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Independent Deployments Are Important
&lt;/h3&gt;

&lt;p&gt;If one team’s release shouldn’t require coordinating with five other teams, independently deployable services can help.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Strong Domain Boundaries Exist
&lt;/h3&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payments
Identity
Orders
Shipping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If these are clearly separated business domains, they may be good candidates for service boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Microservices Are a Bad Idea
&lt;/h3&gt;

&lt;p&gt;Don’t choose microservices simply because:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Netflix uses them.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Our application is getting bigger.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Microservices are more scalable.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microservices may be a bad choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The team is small&lt;/li&gt;
&lt;li&gt;The domain is poorly understood&lt;/li&gt;
&lt;li&gt;You don’t have DevOps maturity&lt;/li&gt;
&lt;li&gt;Monitoring is weak&lt;/li&gt;
&lt;li&gt;Deployment automation is poor&lt;/li&gt;
&lt;li&gt;Services need constant communication&lt;/li&gt;
&lt;li&gt;Data boundaries aren’t clear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every service constantly calls every other service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B
A → C
A → D

B → A
B → C
B → D

C → A
C → B
C → D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you haven’t created independent services.&lt;/p&gt;

&lt;p&gt;You’ve created a &lt;strong&gt;distributed monolith&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Distributed Monolith?
&lt;/h3&gt;

&lt;p&gt;A distributed monolith has multiple services but behaves like one tightly coupled application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order
  ↓
Inventory
  ↓
Payment
  ↓
User
  ↓
Notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request requires several services to be available.&lt;/p&gt;

&lt;p&gt;Deploying one service requires another service to be updated.&lt;/p&gt;

&lt;p&gt;At this point, you’ve taken the complexity of a monolith and added network complexity on top of it.&lt;/p&gt;

&lt;p&gt;That’s one of the worst outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Importance of Service Boundaries
&lt;/h3&gt;

&lt;p&gt;Good microservices have clear responsibilities.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

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

Owns:
- Payments
- Refunds
- Payment status

Doesn't own:
- Orders
- Inventory
- User profiles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service should ideally own its business logic and data.&lt;/p&gt;

&lt;p&gt;This concept is often described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Bounded Context&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;from Domain-Driven Design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don’t Start With the Most Complex Service
&lt;/h3&gt;

&lt;p&gt;When extracting your first service, choose a capability with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear ownership&lt;/li&gt;
&lt;li&gt;Clear boundaries&lt;/li&gt;
&lt;li&gt;Limited dependencies&lt;/li&gt;
&lt;li&gt;Independent scaling requirements&lt;/li&gt;
&lt;li&gt;Minimal cross-service transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t start by extracting the most interconnected component.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Practical Decision Framework
&lt;/h3&gt;

&lt;p&gt;Before choosing microservices, ask:&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 1
&lt;/h3&gt;

&lt;p&gt;Do we actually have a scaling problem?&lt;/p&gt;

&lt;p&gt;If not, don’t introduce distributed complexity just for future scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 2
&lt;/h3&gt;

&lt;p&gt;Do different parts of the application need independent deployments?&lt;/p&gt;

&lt;p&gt;If yes, microservices may help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 3
&lt;/h3&gt;

&lt;p&gt;Do we have clear domain boundaries?&lt;/p&gt;

&lt;p&gt;If no, start with a modular monolith.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 4
&lt;/h3&gt;

&lt;p&gt;Can our team operate distributed systems?&lt;/p&gt;

&lt;p&gt;Do we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;tracing&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;infrastructure automation&lt;/li&gt;
&lt;li&gt;incident response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not, microservices may be premature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Question 5
&lt;/h3&gt;

&lt;p&gt;Can services operate independently?&lt;/p&gt;

&lt;p&gt;If every service requires five other services for every request, reconsider the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Comparison
&lt;/h3&gt;

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

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

&lt;p&gt;Modular Monolith&lt;/p&gt;

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

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

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

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

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

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

&lt;p&gt;Usually one&lt;/p&gt;

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

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

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

&lt;p&gt;Usually shared&lt;/p&gt;

&lt;p&gt;Usually shared&lt;/p&gt;

&lt;p&gt;Usually separate&lt;/p&gt;

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

&lt;p&gt;Application-level&lt;/p&gt;

&lt;p&gt;Application-level&lt;/p&gt;

&lt;p&gt;Service-level&lt;/p&gt;

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

&lt;p&gt;In-process&lt;/p&gt;

&lt;p&gt;In-process&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;p&gt;Team independence&lt;/p&gt;

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

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

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

&lt;p&gt;Infrastructure cost&lt;/p&gt;

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

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

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

&lt;p&gt;Failure isolation&lt;/p&gt;

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

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

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

&lt;p&gt;Initial development speed&lt;/p&gt;

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

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

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

&lt;p&gt;Long-term complexity&lt;/p&gt;

&lt;p&gt;Can increase&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  A Common Misconception
&lt;/h3&gt;

&lt;h3&gt;
  
  
  “Microservices automatically scale better.”
&lt;/h3&gt;

&lt;p&gt;Not necessarily.&lt;/p&gt;

&lt;p&gt;A monolith can scale horizontally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load Balancer
             / | \
            / | \
         App App App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microservices simply provide more granular scaling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → 20 instances
User Service → 3 instances
Payment Service → 8 instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit is flexibility, not magic scalability.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Would I Choose?
&lt;/h3&gt;

&lt;p&gt;For a new application with a small or medium-sized team, I’d generally start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;A well-structured modular monolith.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build clear boundaries.&lt;/p&gt;

&lt;p&gt;Measure the system.&lt;/p&gt;

&lt;p&gt;Understand where the real problems are.&lt;/p&gt;

&lt;p&gt;Then extract services when there is a concrete reason.&lt;/p&gt;

&lt;p&gt;For an organization with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many teams&lt;/li&gt;
&lt;li&gt;clear business domains&lt;/li&gt;
&lt;li&gt;independent deployment requirements&lt;/li&gt;
&lt;li&gt;different scaling requirements&lt;/li&gt;
&lt;li&gt;mature DevOps practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;microservices can be a very effective architecture.&lt;/p&gt;

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

&lt;p&gt;Monoliths and microservices aren’t competing technologies.&lt;/p&gt;

&lt;p&gt;They are architectural choices with different trade-offs.&lt;/p&gt;

&lt;p&gt;A monolith provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simplicity&lt;/li&gt;
&lt;li&gt;easy transactions&lt;/li&gt;
&lt;li&gt;straightforward deployment&lt;/li&gt;
&lt;li&gt;simpler debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microservices provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;independent deployment&lt;/li&gt;
&lt;li&gt;independent scaling&lt;/li&gt;
&lt;li&gt;team autonomy&lt;/li&gt;
&lt;li&gt;stronger service boundaries&lt;/li&gt;
&lt;li&gt;potential fault isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But microservices also introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;network failures&lt;/li&gt;
&lt;li&gt;distributed transactions&lt;/li&gt;
&lt;li&gt;data consistency challenges&lt;/li&gt;
&lt;li&gt;observability requirements&lt;/li&gt;
&lt;li&gt;operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best architecture isn’t the one that sounds the most modern.&lt;/p&gt;

&lt;p&gt;It’s the one that solves your actual problems without introducing unnecessary complexity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Don’t choose microservices because your application is becoming successful. Choose them when the benefits of independence outweigh the complexity of distribution.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you’re starting with a monolith, that doesn’t mean you’ve made a permanent decision. A well-designed modular monolith can be an excellent foundation for gradually extracting services when the need becomes real.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s Next?
&lt;/h3&gt;

&lt;p&gt;Choosing microservices is only the beginning.&lt;/p&gt;

&lt;p&gt;Once an application is split into multiple services, a much bigger question appears:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;How do these services communicate with each other?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Should they use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST APIs?&lt;/li&gt;
&lt;li&gt;gRPC?&lt;/li&gt;
&lt;li&gt;Message queues?&lt;/li&gt;
&lt;li&gt;Kafka?&lt;/li&gt;
&lt;li&gt;RabbitMQ?&lt;/li&gt;
&lt;li&gt;Amazon SQS?&lt;/li&gt;
&lt;li&gt;Events?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach has different trade-offs around latency, reliability, scalability, consistency, and failure handling.&lt;/p&gt;

&lt;p&gt;That’s what we’ll explore in the next article:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microservices Communication: REST vs gRPC vs Message Queues vs Event-Driven Architecture.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>microservicearchitec</category>
      <category>monolithicarchitectu</category>
      <category>rails</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>What Is Vibe Coding? Benefits, Risks, and Best Practices for AI-Assisted Development</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:00:59 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/what-is-vibe-coding-benefits-risks-and-best-practices-for-ai-assisted-development-36ca</link>
      <guid>https://dev.to/yashika_vijayvargiya/what-is-vibe-coding-benefits-risks-and-best-practices-for-ai-assisted-development-36ca</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI has changed how developers write software.&lt;/p&gt;

&lt;p&gt;Instead of writing every line of code manually, many developers now describe what they want in plain English and let AI generate the implementation.&lt;/p&gt;

&lt;p&gt;This way of working has become popularly known as &lt;code&gt;vibe coding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some developers see it as the future of software development, while others worry it encourages developers to rely too heavily on AI.&lt;/p&gt;

&lt;p&gt;So, what exactly is vibe coding, and when should you use it?&lt;/p&gt;

&lt;p&gt;Let's explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Vibe Coding?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vibe coding is an AI-assisted development approach where developers describe what they want to build instead of manually writing every line of code.&lt;/p&gt;

&lt;p&gt;Rather than starting with an empty file, developers provide prompts such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a REST API for managing products with authentication and pagination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI generates the initial implementation, which the developer reviews, modifies, and tests.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of writing every line yourself, you guide AI to write the first draft of your code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Traditional Coding vs. Vibe Coding
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Traditional Development&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Understand the problem&lt;br&gt;
↓&lt;br&gt;
Design the solution&lt;br&gt;
↓&lt;br&gt;
Write code&lt;br&gt;
↓&lt;br&gt;
Debug&lt;br&gt;
↓&lt;br&gt;
Test&lt;br&gt;
↓&lt;br&gt;
Deploy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The developer writes every implementation detail manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Coding
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Understand the problem&lt;br&gt;
↓&lt;br&gt;
Describe it to AI&lt;br&gt;
↓&lt;br&gt;
AI generates code&lt;br&gt;
↓&lt;br&gt;
Review&lt;br&gt;
↓&lt;br&gt;
Test&lt;br&gt;
↓&lt;br&gt;
Improve&lt;br&gt;
↓&lt;br&gt;
Deploy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The developer focuses more on problem-solving and less on typing boilerplate code.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Example
&lt;/h2&gt;

&lt;p&gt;Imagine you want to build authentication in a Rails application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional approach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Generate models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure routes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add controllers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write validations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement sessions or JWT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create tests&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This might take hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe coding approach&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create JWT authentication for a Rails API with login, logout, refresh tokens, request specs, and proper error handling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within seconds, the AI generates the initial implementation.&lt;/p&gt;

&lt;p&gt;Your job becomes reviewing, testing, and refining the code instead of writing everything from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is Vibe Coding Becoming Popular?
&lt;/h2&gt;

&lt;p&gt;Modern AI coding tools can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understand multiple files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate large codebases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactor code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explain existing code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Detect common bugs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows developers to move faster, especially when working on repetitive tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Popular AI Coding Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some of the most widely used AI coding assistants include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cursor&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Claude Code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub Copilot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemini Code Assist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Windsurf&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help developers write, refactor, and understand code more efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits of Vibe Coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faster Development&lt;/strong&gt;&lt;br&gt;
Instead of spending time writing repetitive code, developers can focus on solving business problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Less Boilerplate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI can generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CRUD operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Easier Learning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers exploring a new language or framework can use AI to understand patterns and best practices more quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Productivity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many developers use AI for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Writing tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explaining unfamiliar code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generating documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating migration files&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Risks of Vibe Coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Despite its advantages, vibe coding has important limitations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Can Produce Incorrect Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI-generated code isn't always correct.&lt;/p&gt;

&lt;p&gt;It may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Misuse libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ignore edge cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Introduce bugs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate outdated patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always review the generated code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security Risks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI might generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SQL injection vulnerabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weak authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insecure API implementations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security-critical code should always be reviewed carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overengineering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes AI solves simple problems with unnecessarily complex code.&lt;/p&gt;

&lt;p&gt;Good developers know when a simpler solution is better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduced Understanding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If developers accept AI-generated code without understanding it, debugging and maintaining the application becomes much harder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Should You Use Vibe Coding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vibe coding works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CRUD applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API scaffolding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code explanations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repetitive development tasks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When Should You Avoid It?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Be cautious when working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Payment systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authorization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Financial calculations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security-sensitive features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distributed systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance-critical code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These areas require careful design, testing, and review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use AI as a collaborator&lt;br&gt;
Don't treat AI as a replacement for engineering judgment.&lt;/p&gt;

&lt;p&gt;Review every generated change&lt;br&gt;
Understand what the AI generated before merging it.&lt;/p&gt;

&lt;p&gt;Write tests&lt;br&gt;
AI-generated code should be validated with automated tests.&lt;/p&gt;

&lt;p&gt;Keep prompts specific&lt;br&gt;
Instead of asking:&lt;/p&gt;

&lt;p&gt;Build an API.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;Build a Rails API using service objects, request specs, pagination, JWT authentication, and proper error handling.&lt;/p&gt;

&lt;p&gt;Clear prompts generally produce better results.&lt;/p&gt;

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

&lt;p&gt;Vibe coding is changing the software development workflow by allowing developers to express ideas in natural language and use AI to generate an initial implementation.&lt;/p&gt;

&lt;p&gt;Used thoughtfully, it can improve productivity and reduce repetitive work.&lt;/p&gt;

&lt;p&gt;However, AI-generated code is still software that needs to be reviewed, tested, and maintained.&lt;/p&gt;

&lt;p&gt;The most effective developers aren't those who let AI write everything—they're the ones who know when to rely on AI and when to rely on their own expertise.&lt;/p&gt;

&lt;p&gt;Hashnode AI&lt;/p&gt;

&lt;p&gt;Hashnode AI is a Pro feature&lt;br&gt;
Upgrade to Pro to refine your drafts, suggest edits, and brainstorm with the Writing Assistant.&lt;/p&gt;

&lt;p&gt;Upgrade to Pro&lt;br&gt;
Search Hashnode&lt;br&gt;
Search posts, tags, users, and pages&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Rails Bug That Disappeared After a Server Restart — Until I Found the Mutation</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Mon, 17 Aug 2026 07:16:44 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/the-rails-bug-that-disappeared-after-a-server-restart-until-i-found-the-mutation-5bo6</link>
      <guid>https://dev.to/yashika_vijayvargiya/the-rails-bug-that-disappeared-after-a-server-restart-until-i-found-the-mutation-5bo6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;Some bugs are difficult because the code is complicated.&lt;/p&gt;

&lt;p&gt;Others are difficult because the bug seems to have no rules.&lt;/p&gt;

&lt;p&gt;This was the second kind.&lt;/p&gt;

&lt;p&gt;A Rails application I was working on started rendering ActiveAdmin menus incorrectly.&lt;/p&gt;

&lt;p&gt;Sometimes nested menu items appeared under the wrong level.&lt;/p&gt;

&lt;p&gt;Sometimes everything looked completely normal.&lt;/p&gt;

&lt;p&gt;And sometimes restarting the application made the problem disappear.&lt;/p&gt;

&lt;p&gt;That last part was the most suspicious.&lt;/p&gt;

&lt;p&gt;A restart shouldn't "fix" a deterministic piece of Ruby code.&lt;/p&gt;

&lt;p&gt;So I started digging.&lt;/p&gt;

&lt;p&gt;What I eventually found was a much more interesting problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An object was being modified somewhere I didn't expect it to be modified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the fix turned out to be one of the simplest Ruby changes I've made:&lt;br&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But getting there took considerably more work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The application was using ActiveAdmin for its administrative interface.&lt;/p&gt;

&lt;p&gt;The problem appeared in the navigation menu.&lt;/p&gt;

&lt;p&gt;We had nested menu items, and occasionally they were rendered as top-level items instead of being nested under their intended parent.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

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

Reports
  ├── Sales
  └── Revenue

Actual:

Reports
Sales
Revenue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frustrating part was that the behavior wasn't consistently reproducible.&lt;/p&gt;

&lt;p&gt;The same application could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;render the menu correctly&lt;/li&gt;
&lt;li&gt;render it incorrectly&lt;/li&gt;
&lt;li&gt;restart&lt;/li&gt;
&lt;li&gt;start working again&lt;/li&gt;
&lt;li&gt;continue working for some time&lt;/li&gt;
&lt;li&gt;then fail again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not the kind of bug where I could simply open the relevant controller and find a typo.&lt;/p&gt;

&lt;p&gt;It looked almost random.&lt;/p&gt;

&lt;h3&gt;
  
  
  The First Clue: Restarting the Server "Fixed" It
&lt;/h3&gt;

&lt;p&gt;The most interesting observation was that restarting the server temporarily resolved the problem.&lt;/p&gt;

&lt;p&gt;That immediately made me suspicious of &lt;strong&gt;state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If restarting the process changes the behavior without changing the database or application code, something may be surviving longer than expected inside the process.&lt;/p&gt;

&lt;p&gt;That led me away from asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What condition makes the menu render incorrectly?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and toward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What state is being mutated between requests?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was a much better question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following the Menu Construction
&lt;/h2&gt;

&lt;p&gt;I started tracing how ActiveAdmin constructs its menus.&lt;/p&gt;

&lt;p&gt;Eventually I reached the menu node implementation and the &lt;code&gt;add&lt;/code&gt; method responsible for adding menu items.&lt;/p&gt;

&lt;p&gt;The important part looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# menu item construction&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, there was nothing obviously wrong.&lt;/p&gt;

&lt;p&gt;The method received an &lt;code&gt;options&lt;/code&gt; object and used it to construct the menu node.&lt;/p&gt;

&lt;p&gt;But the more I followed the execution path, the more suspicious the object itself became.&lt;/p&gt;

&lt;p&gt;The same options object was being passed through multiple parts of the menu-building process.&lt;/p&gt;

&lt;p&gt;And something was changing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Wasn't the Value
&lt;/h2&gt;

&lt;p&gt;This was the key realization.&lt;/p&gt;

&lt;p&gt;The problem wasn't necessarily that the options contained the wrong value.&lt;/p&gt;

&lt;p&gt;The problem was that &lt;strong&gt;the options object itself was mutable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine this simplified 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="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="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;label: &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You pass that object into another method:&lt;br&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;build_menu&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;build_menu&lt;/code&gt; modifies 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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the original object has now changed too.&lt;/p&gt;

&lt;p&gt;There is no copy.&lt;/p&gt;

&lt;p&gt;There is no isolation.&lt;/p&gt;

&lt;p&gt;Both pieces of code are holding a reference to the same object.&lt;/p&gt;

&lt;p&gt;So 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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns:&lt;br&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;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even though the code that originally created the options never intentionally removed it.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of mutation that can create a bug that looks random.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Looked Random
&lt;/h2&gt;

&lt;p&gt;The menu wasn't necessarily broken every time.&lt;/p&gt;

&lt;p&gt;The behavior depended on &lt;strong&gt;when and how the shared object was mutated&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That made the symptoms particularly confusing.&lt;/p&gt;

&lt;p&gt;A simplified lifecycle looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create menu options
        ↓
Pass options to ActiveAdmin
        ↓
ActiveAdmin modifies options
        ↓
Original object is now different
        ↓
Another menu operation uses it
        ↓
Parent information is missing/incorrect
        ↓
Nested item becomes top-level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because the object lived inside the running Ruby process, restarting the server cleared that in-memory state.&lt;/p&gt;

&lt;p&gt;That explained why a restart could appear to "fix" the bug.&lt;/p&gt;

&lt;p&gt;It wasn't fixing anything.&lt;/p&gt;

&lt;p&gt;It was simply giving us a fresh process with fresh objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I Found the GitHub Issue
&lt;/h2&gt;

&lt;p&gt;After spending a significant amount of time debugging the behavior at the application and dependency level, I searched GitHub for similar ActiveAdmin problems.&lt;/p&gt;

&lt;p&gt;That's when I found:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ActiveAdmin issue #8078 — "Nested menu items are rendered top-level."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The issue description was remarkably similar.&lt;/p&gt;

&lt;p&gt;It reported that nested menus were sometimes rendered at the top level, and that restarting the server temporarily solved the problem. The issue was difficult to reproduce and had been observed in production.&lt;/p&gt;

&lt;p&gt;That was the moment when the investigation changed direction.&lt;/p&gt;

&lt;p&gt;I wasn't dealing with some mysterious Rails rendering bug.&lt;/p&gt;

&lt;p&gt;There was already evidence that ActiveAdmin's menu construction could be modifying menu state.&lt;/p&gt;

&lt;p&gt;The issue eventually led to ActiveAdmin PR #8132, titled:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Make sure menu creation does not modify menu options."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ActiveAdmin maintainer later reported that the patch had run in production for almost a month without the menu rendering problem recurring before merging it.&lt;/p&gt;

&lt;p&gt;Now I had a much stronger hypothesis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The options object was being mutated during menu construction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next question was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I prevent that mutation from affecting the object my application is using?&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Fix: Give &lt;code&gt;add&lt;/code&gt; Its Own Copy
&lt;/h3&gt;

&lt;p&gt;Instead of passing the original options object into the existing implementation, I created a duplicate first.&lt;/p&gt;

&lt;p&gt;The patch was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MenuNode&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
    &lt;span class="k"&gt;super&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="k"&gt;end&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;Menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;MenuNode&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The important line is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    │
    ▼
options ───────────────┐
                      │
                      ▼
                ActiveAdmin
                      │
                      ▼
                modifies object
                      │
                      ▼
            original options changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    │
    ▼
original options

    │
    │ dup
    ▼

copied options
    │
    ▼
ActiveAdmin
    │
    ▼
can modify its copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original object remains untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;dup&lt;/code&gt; Fixed It
&lt;/h2&gt;

&lt;p&gt;Ruby objects are references.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;label: &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;copy&lt;/code&gt; isn't a new Hash.&lt;/p&gt;

&lt;p&gt;Both variables point to the same object.&lt;/p&gt;

&lt;p&gt;So:&lt;br&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;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;also changes:&lt;br&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;options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because they're the same object.&lt;/p&gt;

&lt;p&gt;But:&lt;br&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;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a separate Hash.&lt;/p&gt;

&lt;p&gt;Now:&lt;br&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;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't remove the key from the original &lt;code&gt;options&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's exactly the isolation we needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Used &lt;code&gt;super&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There was another important detail in the fix.&lt;/p&gt;

&lt;p&gt;I didn't want to reimplement ActiveAdmin's &lt;code&gt;add&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;That would have created another maintenance problem.&lt;/p&gt;

&lt;p&gt;Instead, I wanted to change &lt;strong&gt;one thing&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give the original method a safe copy of the options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then let ActiveAdmin continue doing everything else exactly as it already did.&lt;/p&gt;

&lt;p&gt;That's why the implementation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
  &lt;span class="k"&gt;super&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Our wrapper
    ↓
duplicate options
    ↓
ActiveAdmin's original add
    ↓
existing behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a useful pattern when working with a third-party library:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the boundary, not the library's internal behavior.&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Debugging Lesson: Don't Trust "Random"
&lt;/h3&gt;

&lt;p&gt;One of the biggest lessons from this bug was that "random" doesn't necessarily mean random.&lt;/p&gt;

&lt;p&gt;When an application behaves differently after a restart, there is often state involved.&lt;/p&gt;

&lt;p&gt;That state could live in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;class variables&lt;/li&gt;
&lt;li&gt;global objects&lt;/li&gt;
&lt;li&gt;memoized values&lt;/li&gt;
&lt;li&gt;caches&lt;/li&gt;
&lt;li&gt;singleton instances&lt;/li&gt;
&lt;li&gt;mutable configuration&lt;/li&gt;
&lt;li&gt;shared Hashes or Arrays&lt;/li&gt;
&lt;li&gt;library objects that survive across requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Ruby, mutable objects make this especially important.&lt;/p&gt;

&lt;p&gt;This:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;look similar.&lt;/p&gt;

&lt;p&gt;But they create very different ownership semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dependency Was Part of the Application
&lt;/h2&gt;

&lt;p&gt;Another lesson was about debugging third-party libraries.&lt;/p&gt;

&lt;p&gt;It is tempting to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The bug is in ActiveAdmin."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that doesn't help when you're responsible for the application.&lt;/p&gt;

&lt;p&gt;The useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where does the incorrect state enter my application?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That led me through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application behavior
       ↓
ActiveAdmin
       ↓
Menu construction
       ↓
MenuNode#add
       ↓
options object
       ↓
unexpected mutation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I could see the mutation boundary, the fix became straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix Was Tiny. The Investigation Wasn't.
&lt;/h2&gt;

&lt;p&gt;This is something I've noticed repeatedly when debugging production Rails applications.&lt;/p&gt;

&lt;p&gt;The final patch often looks deceptively simple.&lt;/p&gt;

&lt;p&gt;In this case:&lt;br&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is almost trivial.&lt;/p&gt;

&lt;p&gt;But the path to that line involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reproducing an intermittent problem&lt;/li&gt;
&lt;li&gt;comparing behavior before and after a restart&lt;/li&gt;
&lt;li&gt;tracing ActiveAdmin internals&lt;/li&gt;
&lt;li&gt;inspecting how menu nodes were constructed&lt;/li&gt;
&lt;li&gt;questioning object ownership&lt;/li&gt;
&lt;li&gt;searching existing GitHub issues&lt;/li&gt;
&lt;li&gt;finding a closely related ActiveAdmin issue&lt;/li&gt;
&lt;li&gt;understanding why the existing implementation could mutate shared state&lt;/li&gt;
&lt;li&gt;introducing a minimal wrapper&lt;/li&gt;
&lt;li&gt;delegating back to the original implementation with &lt;code&gt;super&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difficulty wasn't writing the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The difficulty was finding the right layer to fix.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Taught Me About Ruby
&lt;/h2&gt;

&lt;p&gt;Ruby makes it very easy to pass objects around.&lt;/p&gt;

&lt;p&gt;That's one of its strengths.&lt;/p&gt;

&lt;p&gt;But that convenience also means that mutable objects can cross boundaries without making ownership obvious.&lt;/p&gt;

&lt;p&gt;Whenever I see a method receiving a Hash or Array, I now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who owns this object, and is this method allowed to modify it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer isn't clear, copying the object at the boundary can sometimes be the difference between stable behavior and an intermittent production bug.&lt;/p&gt;

&lt;p&gt;Of course, &lt;code&gt;dup&lt;/code&gt; isn't a universal solution.&lt;/p&gt;

&lt;p&gt;It is a shallow copy, so nested mutable objects can still be shared:&lt;br&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;menu: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that situation:&lt;br&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;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;duplicates the outer Hash, but the nested &lt;code&gt;:menu&lt;/code&gt; Hash is still shared.&lt;/p&gt;

&lt;p&gt;So the correct solution depends on what the called code actually mutates.&lt;/p&gt;

&lt;p&gt;In this case, duplicating the options object at the &lt;code&gt;add&lt;/code&gt; boundary was enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;When a bug looks random, don't immediately assume the framework is behaving unpredictably.&lt;/p&gt;

&lt;p&gt;Look for state.&lt;/p&gt;

&lt;p&gt;When restarting the server makes the bug disappear, don't celebrate.&lt;/p&gt;

&lt;p&gt;Ask what state the restart destroyed.&lt;/p&gt;

&lt;p&gt;When a dependency receives a mutable object, don't assume it will leave it untouched.&lt;/p&gt;

&lt;p&gt;And when you finally identify the problem, resist the temptation to rewrite everything.&lt;/p&gt;

&lt;p&gt;Sometimes the safest fix is simply:&lt;br&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;span class="k"&gt;super&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tiny boundary.&lt;/p&gt;

&lt;p&gt;A separate object.&lt;/p&gt;

&lt;p&gt;No accidental mutation.&lt;/p&gt;

&lt;p&gt;And suddenly the "random" bug isn't random anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The most valuable part of debugging isn't always the final code change.&lt;/p&gt;

&lt;p&gt;Sometimes it's the moment when a confusing symptom finally becomes a predictable consequence of one small assumption.&lt;/p&gt;

&lt;p&gt;In my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Menus randomly render incorrectly"
              ↓
"Restarting fixes it"
              ↓
"There must be state"
              ↓
"ActiveAdmin is modifying menu options"
              ↓
"The same mutable object is being reused"
              ↓
"Give ActiveAdmin a copy"
              ↓
options.dup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final fix was one line.&lt;/p&gt;

&lt;p&gt;The investigation was the real engineering work.&lt;/p&gt;

&lt;p&gt;And that's probably the part of this bug I'll remember.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>activeadmin</category>
      <category>opensource</category>
    </item>
    <item>
      <title>When a String Assumption Broke Newsletter Tag Filtering in Ruby</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 14 Aug 2026 10:55:37 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/when-a-string-assumption-broke-newsletter-tag-filtering-in-ruby-3ak3</link>
      <guid>https://dev.to/yashika_vijayvargiya/when-a-string-assumption-broke-newsletter-tag-filtering-in-ruby-3ak3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Single Type Assumption Broke Newsletter Tag Filtering in Ruby
&lt;/h2&gt;

&lt;p&gt;A bug doesn't always need hundreds of lines of code to cause a real problem.&lt;/p&gt;

&lt;p&gt;Sometimes, one assumption is enough.&lt;/p&gt;

&lt;p&gt;In this case, the assumption was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Every tag is a String."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That assumption lived inside a &lt;code&gt;Ruby filtering method&lt;/code&gt; in the open-source RubyEvents project.&lt;/p&gt;

&lt;p&gt;It worked perfectly — until a tag wasn't a String.&lt;/p&gt;

&lt;p&gt;That small mismatch was enough to turn a normal user interaction into an &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the story of how the bug was investigated, what was actually going wrong, and how a small change made the filtering logic safer and clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The issue was reported as:&lt;/p&gt;

&lt;h2&gt;
  
  
  "Cannot Click tags on Newsletter"
&lt;/h2&gt;

&lt;p&gt;The behavior was straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a newsletter.&lt;/li&gt;
&lt;li&gt;Click one of its tags.&lt;/li&gt;
&lt;li&gt;Instead of getting the filtered announcements, the application raises an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue was tracked as &lt;strong&gt;#1836&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, this sounds like it could be a routing or controller problem.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;The failure was deeper in the &lt;code&gt;tag-filtering logic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finding the Assumption&lt;/p&gt;

&lt;p&gt;The filtering logic lived in&lt;br&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;Announcement&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="c1"&gt;#by_tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original implementation was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intent is easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take all tags from an announcement&lt;/li&gt;
&lt;li&gt;convert them to lowercase&lt;/li&gt;
&lt;li&gt;compare them with the requested tag&lt;/li&gt;
&lt;li&gt;return matching announcements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For normal String values, this works perfectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;["Ruby", "Rails"].map(&amp;amp;:downcase)
&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is an important assumption hidden inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every element must respond to &lt;strong&gt;downcase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's not necessarily true.&lt;/p&gt;

&lt;p&gt;Imagine the collection contains:&lt;br&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="s2"&gt;"Ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling:&lt;br&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="s2"&gt;"Ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;eventually tries to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And integers don't have a &lt;code&gt;downcase&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The result is a &lt;strong&gt;NoMethodError&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the real problem wasn't the newsletter link itself.&lt;/p&gt;

&lt;p&gt;The newsletter was simply the path that exposed an unsafe assumption in the &lt;code&gt;tag-filtering code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Interesting Part&lt;/strong&gt;: Fixing the Bug Without Changing the Behavior&lt;/p&gt;

&lt;p&gt;There were several ways this could have been "&lt;code&gt;fixed&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;For example, we could have assumed that all tags should always be Strings and modified the data at the source.&lt;/p&gt;

&lt;p&gt;But that would increase the scope of the change.&lt;/p&gt;

&lt;p&gt;The filtering method already had a clear responsibility:&lt;/p&gt;

&lt;p&gt;Find announcements whose tags match the requested tag, case-insensitively.&lt;/p&gt;

&lt;p&gt;So instead of changing the data source, I wanted to make the comparison resilient to the values it actually received.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The implementation became:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two important changes here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;to_s Creates a Safe Comparison Boundary&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Instead of assuming:&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;we explicitly convert the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a String remains a String:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;"Ruby".to_s
# =&amp;gt; "Ruby"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a non-String value becomes safely comparable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;123.to_s
# =&amp;gt; "123"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filtering code no longer crashes simply because a tag value isn't already a String.&lt;/p&gt;

&lt;p&gt;This is a small example of something I find important when working with Ruby:&lt;/p&gt;

&lt;p&gt;Don't make an assumption about the type of an object if you don't actually need that assumption.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;casecmp? Expresses the Actual Requirement&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original implementation lowercased both sides:&lt;br&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;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;But the actual requirement isn't:&lt;/p&gt;

&lt;p&gt;Convert everything to lowercase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The requirement is:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare these values without considering case.&lt;/p&gt;

&lt;p&gt;Ruby provides exactly that 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="n"&gt;casecmp?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the comparison becomes:&lt;br&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;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This makes the intent clearer.&lt;/p&gt;

&lt;p&gt;We're not transforming the data just to compare it.&lt;/p&gt;

&lt;p&gt;We're performing a case-insensitive comparison.&lt;/p&gt;

&lt;p&gt;Why &lt;code&gt;any?&lt;/code&gt; Instead of &lt;code&gt;map?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This was another small but meaningful improvement.&lt;/p&gt;

&lt;p&gt;The old implementation transformed every tag:&lt;br&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;But we don't actually need a new array.&lt;/p&gt;

&lt;p&gt;We only need to answer one question:&lt;/p&gt;

&lt;p&gt;Does at least one tag match?&lt;/p&gt;

&lt;p&gt;That's exactly what any? communicates:&lt;br&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&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;It also allows Ruby to stop checking once a match is found.&lt;/p&gt;

&lt;p&gt;So the new implementation isn't simply more defensive.&lt;/p&gt;

&lt;p&gt;It's also closer to the actual intent of the operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before vs After&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hidden assumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every tag
   ↓
must respond to #downcase

If one doesn't:

NoMethodError
   ↓
request fails
   ↓
user cannot follow the newsletter tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tag value
   ↓
convert safely to String
   ↓
case-insensitive comparison
   ↓
match / no match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing filtering behavior remains intact while the unsafe type assumption is removed.&lt;/p&gt;

&lt;p&gt;Why This Bug Was Easy to Miss&lt;/p&gt;

&lt;p&gt;This is what I found most interesting about the issue.&lt;/p&gt;

&lt;p&gt;The original code isn't obviously bad.&lt;/p&gt;

&lt;p&gt;For a dataset containing only Strings, this is perfectly reasonable Ruby:&lt;br&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The problem only appears when the runtime data doesn't match the assumption made by the implementation.&lt;/p&gt;

&lt;p&gt;That's a common class of bugs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code assumption
       ↓
"this value will always be a String"
       ↓
Works for normal data
       ↓
Unexpected value enters the system
       ↓
Runtime failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson isn't:&lt;/p&gt;

&lt;p&gt;"Never use downcase."&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;Know where your assumptions about data types are coming from.&lt;/p&gt;

&lt;p&gt;If a method operates on data that can contain different types, the boundary where those values are consumed should be resilient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Investigation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What made this issue useful as a debugging exercise was that the final code change was small.&lt;/p&gt;

&lt;p&gt;The investigation was the interesting part.&lt;/p&gt;

&lt;p&gt;Instead of only looking at the error message, I traced the behavior back to the collection filtering logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That led to a few questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What type can each tag actually contain?&lt;/li&gt;
&lt;li&gt;Why does downcase fail?&lt;/li&gt;
&lt;li&gt;Do we really need to transform every tag?&lt;/li&gt;
&lt;li&gt;Can the comparison itself be made type-safe?&lt;/li&gt;
&lt;li&gt;Can the existing behavior be preserved without changing the surrounding code?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those questions were answered, the fix became much simpler.&lt;/p&gt;

&lt;p&gt;The problem wasn't complicated business logic.&lt;/p&gt;

&lt;p&gt;It was an unsafe assumption about the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Final Change&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual change was intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- Collection.new(select { |a| a.tags.map(&amp;amp;:downcase).include?(tag.downcase) })
&lt;/span&gt;&lt;span class="gi"&gt;+ Collection.new(select { |a| a.tags.any? { |tag_value| tag_value.to_s.casecmp?(tag) } })
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line changed.&lt;/p&gt;

&lt;p&gt;But that one line removed the assumption that every tag value was already a String.&lt;/p&gt;

&lt;p&gt;I also fixed a linting issue in a follow-up commit.&lt;/p&gt;

&lt;p&gt;The pull request was reviewed by the RubyEvents maintainer, passed all checks, and was merged into the project's main branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1836 — Cannot Click tags on Newsletter&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull Request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1847 — Fix tag filtering for non-string tags&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm Proud Of&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final diff is tiny.&lt;/p&gt;

&lt;p&gt;That's actually what I like about this fix.&lt;/p&gt;

&lt;p&gt;There was no need to rewrite the filtering system or introduce another abstraction.&lt;/p&gt;

&lt;p&gt;The existing behavior was already correct for valid String values.&lt;/p&gt;

&lt;p&gt;The problem was the assumption around those values.&lt;/p&gt;

&lt;p&gt;So instead of changing the entire flow, the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps the existing behavior&lt;/li&gt;
&lt;li&gt;makes the comparison type-safe&lt;/li&gt;
&lt;li&gt;avoids unnecessary array allocation&lt;/li&gt;
&lt;li&gt;expresses the intent with any?&lt;/li&gt;
&lt;li&gt;uses casecmp? for case-insensitive comparison&lt;/li&gt;
&lt;li&gt;keeps the change isolated to the filtering method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small change, focused responsibility.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Small bugs can expose bigger assumptions&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code wasn't complicated.&lt;/p&gt;

&lt;p&gt;The assumption behind the code was the real problem.&lt;/p&gt;

&lt;p&gt;Whenever I see code such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;items&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;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:some_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I now ask myself:&lt;/p&gt;

&lt;p&gt;Do I know for certain that every item responds to this method?&lt;/p&gt;

&lt;p&gt;That question becomes particularly important at boundaries where data may come from different sources.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fix the behavior, not just the exception&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It would have been easy to focus only on preventing the &lt;strong&gt;NoMethodError&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But a good fix should do more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preserve existing behavior&lt;/li&gt;
&lt;li&gt;make the comparison safe&lt;/li&gt;
&lt;li&gt;make the intent clearer&lt;/li&gt;
&lt;li&gt;avoid unnecessary transformations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final implementation does all four.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Express the question you're actually asking&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compare:&lt;br&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;map&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="nf"&gt;include?&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;with:&lt;br&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;any?&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 second version tells the reader what we're actually asking.&lt;/p&gt;

&lt;p&gt;We're not interested in producing another collection.&lt;/p&gt;

&lt;p&gt;We're asking whether any tag matches.&lt;/p&gt;

&lt;p&gt;That makes the code easier to reason about and maintain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open source debugging is different&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing I enjoy about contributing to open source is that you get to work with code outside the assumptions of your own applications.&lt;/p&gt;

&lt;p&gt;You don't necessarily know every historical decision behind a method.&lt;/p&gt;

&lt;p&gt;You don't know every shape of data that has passed through it.&lt;/p&gt;

&lt;p&gt;And you don't get to rewrite the whole system just because you find one imperfect assumption.&lt;/p&gt;

&lt;p&gt;You have to understand the existing behavior, make the smallest responsible change, and verify that the fix doesn't break what was already working.&lt;/p&gt;

&lt;p&gt;That's what made this issue interesting to me.&lt;/p&gt;

&lt;p&gt;The final diff was tiny.&lt;/p&gt;

&lt;p&gt;The reasoning behind it was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A bug doesn't always announce itself with a complicated stack trace or a thousand-line fix.&lt;/p&gt;

&lt;p&gt;Sometimes it's hidden inside a single assumption:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"Every tag is a String."&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
When that assumption stopped being true, clicking a newsletter tag stopped working.&lt;/p&gt;

&lt;p&gt;The fix was to make the comparison type-safe, preserve case-insensitive matching, and express the filtering intent more directly.&lt;/p&gt;

&lt;p&gt;One line changed. One user-facing failure removed. One more reminder that robust software is often about handling the values we didn't expect.&lt;/p&gt;

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

&lt;p&gt;Issue:&lt;br&gt;
1836 — Cannot Click tags on Newsletter&lt;/p&gt;

&lt;p&gt;Pull Request:&lt;br&gt;
1847 — Fix tag filtering for non-string tags&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>ruby</category>
      <category>opensource</category>
      <category>debugging</category>
    </item>
    <item>
      <title>What Is Ponytail? A Beginner’s Guide to the AI Coding Companion Everyone Is Talking About</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 05 Aug 2026 09:29:26 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/what-is-ponytail-a-beginners-guide-to-the-ai-coding-companion-everyone-is-talking-about-1d6g</link>
      <guid>https://dev.to/yashika_vijayvargiya/what-is-ponytail-a-beginners-guide-to-the-ai-coding-companion-everyone-is-talking-about-1d6g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;⚠️ Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;AI developer tools evolve incredibly fast. Installation steps, supported editors, AI models, and configuration options may change over time. This article is&lt;/em&gt; &lt;strong&gt;&lt;em&gt;not intended to be official documentation&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;. Instead, its goal is to help you understand&lt;/em&gt; &lt;strong&gt;&lt;em&gt;what Ponytail is, why it exists, and when you might want to use it&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;. Before installing or configuring Ponytail, always refer to its official documentation for the latest instructions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;If you’ve recently spent time on X (Twitter), Reddit, or developer communities, you’ve probably seen people talking about &lt;strong&gt;Ponytail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some developers call it an AI coding assistant.&lt;/p&gt;

&lt;p&gt;Others think it’s an alternative to Cursor.&lt;/p&gt;

&lt;p&gt;Some believe it’s a replacement for Claude Code.&lt;/p&gt;

&lt;p&gt;And many assume it’s another Large Language Model (LLM) like ChatGPT or Claude.&lt;/p&gt;

&lt;p&gt;None of these descriptions are entirely accurate.&lt;/p&gt;

&lt;p&gt;This confusion exists because Ponytail is different from most AI tools developers are familiar with.&lt;/p&gt;

&lt;p&gt;In this article, we’ll break down exactly what Ponytail is, what it is not, how it fits into the AI coding ecosystem, and when it makes sense to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Biggest Misconception About Ponytail
&lt;/h3&gt;

&lt;p&gt;The first thing to understand is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ponytail is not an AI model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It cannot answer questions by itself.&lt;/p&gt;

&lt;p&gt;It cannot generate code by itself.&lt;/p&gt;

&lt;p&gt;It cannot replace ChatGPT, Claude, Gemini, or any other language model.&lt;/p&gt;

&lt;p&gt;Instead, Ponytail works &lt;strong&gt;alongside&lt;/strong&gt; an AI coding agent.&lt;/p&gt;

&lt;p&gt;Think of it as a layer that influences how the AI approaches programming tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the AI Coding Ecosystem
&lt;/h3&gt;

&lt;p&gt;Many developers mix together editors, AI models, and coding agents.&lt;/p&gt;

&lt;p&gt;These are different pieces of the puzzle.&lt;/p&gt;

&lt;p&gt;Imagine you’re building a Rails application.&lt;/p&gt;

&lt;p&gt;You have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code
    ↓
AI Coding Agent
    ↓
LLM (Claude / GPT / Gemini)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a different responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code
&lt;/h3&gt;

&lt;p&gt;VS Code is simply your editor.&lt;/p&gt;

&lt;p&gt;It doesn’t understand your code or generate anything on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Model
&lt;/h3&gt;

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

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

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

&lt;p&gt;These are language models.&lt;/p&gt;

&lt;p&gt;They understand natural language and generate code.&lt;/p&gt;

&lt;p&gt;However, by themselves they don’t know how to edit files inside your project or interact with your development environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Coding Agent
&lt;/h3&gt;

&lt;p&gt;An AI coding agent connects your editor to an AI model.&lt;/p&gt;

&lt;p&gt;It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read project files&lt;/li&gt;
&lt;li&gt;Edit code&lt;/li&gt;
&lt;li&gt;Create new files&lt;/li&gt;
&lt;li&gt;Run commands&lt;/li&gt;
&lt;li&gt;Analyze your repository&lt;/li&gt;
&lt;li&gt;Suggest refactoring&lt;/li&gt;
&lt;li&gt;Generate tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include various AI-powered coding assistants and editor integrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ponytail
&lt;/h3&gt;

&lt;p&gt;Ponytail sits on top of this workflow.&lt;/p&gt;

&lt;p&gt;Instead of replacing your AI, it attempts to improve &lt;strong&gt;how the AI behaves while writing code&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Analogy
&lt;/h3&gt;

&lt;p&gt;Imagine you’re hiring a software engineer.&lt;/p&gt;

&lt;p&gt;Without guidance, they might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create unnecessary abstractions&lt;/li&gt;
&lt;li&gt;introduce extra classes&lt;/li&gt;
&lt;li&gt;over-engineer a simple solution&lt;/li&gt;
&lt;li&gt;use complex design patterns for small problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine giving that engineer a document that says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep things simple.&lt;/li&gt;
&lt;li&gt;Don’t create classes unless necessary.&lt;/li&gt;
&lt;li&gt;Prefer existing libraries.&lt;/li&gt;
&lt;li&gt;Avoid unnecessary complexity.&lt;/li&gt;
&lt;li&gt;Write code another developer can understand quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engineer hasn’t changed.&lt;/p&gt;

&lt;p&gt;Their instructions have.&lt;/p&gt;

&lt;p&gt;Ponytail plays a similar role for AI coding assistants.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Problem Does Ponytail Solve?
&lt;/h3&gt;

&lt;p&gt;As AI became better at writing code, developers noticed recurring issues.&lt;/p&gt;

&lt;p&gt;The AI often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates more files than necessary&lt;/li&gt;
&lt;li&gt;writes excessive boilerplate&lt;/li&gt;
&lt;li&gt;introduces unnecessary abstractions&lt;/li&gt;
&lt;li&gt;overuses design patterns&lt;/li&gt;
&lt;li&gt;invents helper classes&lt;/li&gt;
&lt;li&gt;makes small tasks more complicated than they need to be&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes this is useful.&lt;/p&gt;

&lt;p&gt;Often it isn’t.&lt;/p&gt;

&lt;p&gt;Ponytail aims to steer the AI toward simpler, more maintainable solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Ponytail Is NOT
&lt;/h3&gt;

&lt;p&gt;Ponytail is  &lt;strong&gt;not&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;another version of ChatGPT&lt;/li&gt;
&lt;li&gt;another LLM&lt;/li&gt;
&lt;li&gt;a replacement for Claude&lt;/li&gt;
&lt;li&gt;a replacement for Cursor&lt;/li&gt;
&lt;li&gt;an IDE&lt;/li&gt;
&lt;li&gt;a code editor&lt;/li&gt;
&lt;li&gt;a compiler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this distinction removes most of the confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Ponytail Work Without an AI?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;If you only install Ponytail, nothing happens.&lt;/p&gt;

&lt;p&gt;You still need an AI coding agent backed by an AI model.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;br&gt;
&lt;/p&gt;

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

VS Code + Ponytail
❌

VS Code + AI Agent
✅

VS Code + AI Agent + Ponytail
✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Does Ponytail Replace Cursor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Cursor is a complete AI-powered editor.&lt;/p&gt;

&lt;p&gt;Ponytail is not.&lt;/p&gt;

&lt;p&gt;Cursor provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;editor integration&lt;/li&gt;
&lt;li&gt;AI chat&lt;/li&gt;
&lt;li&gt;autocomplete&lt;/li&gt;
&lt;li&gt;repository understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ponytail focuses on influencing how an AI coding assistant approaches problems rather than replacing the editor itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail Replace Claude?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Claude is the intelligence.&lt;/p&gt;

&lt;p&gt;Ponytail provides guidance.&lt;/p&gt;

&lt;p&gt;Without Claude (or another supported model), Ponytail has nothing to reason with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail Reduce Token Usage?
&lt;/h3&gt;

&lt;p&gt;This is one of the most common questions.&lt;/p&gt;

&lt;p&gt;The answer is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not directly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ponytail does not magically reduce the number of tokens consumed.&lt;/p&gt;

&lt;p&gt;However, by encouraging the AI to produce smaller, simpler solutions with fewer unnecessary edits, some workflows may naturally involve fewer generated tokens.&lt;/p&gt;

&lt;p&gt;This depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the AI model&lt;/li&gt;
&lt;li&gt;the coding task&lt;/li&gt;
&lt;li&gt;the prompts&lt;/li&gt;
&lt;li&gt;the agent’s behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So any reduction is a side effect of simpler outputs, not a guaranteed feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Ponytail Free?
&lt;/h3&gt;

&lt;p&gt;Ponytail itself has generally been made available as an open-source project.&lt;/p&gt;

&lt;p&gt;However, remember:&lt;/p&gt;

&lt;p&gt;Using Ponytail does &lt;strong&gt;not&lt;/strong&gt; eliminate the need for an AI model.&lt;/p&gt;

&lt;p&gt;If your coding assistant relies on a paid AI service or API, those costs still apply.&lt;/p&gt;

&lt;p&gt;Think of Ponytail as improving the workflow rather than replacing the underlying AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Use Ponytail?
&lt;/h3&gt;

&lt;p&gt;Ponytail can be valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;working on large production codebases&lt;/li&gt;
&lt;li&gt;contributing to open-source projects&lt;/li&gt;
&lt;li&gt;maintaining mature Rails applications&lt;/li&gt;
&lt;li&gt;reviewing AI-generated code&lt;/li&gt;
&lt;li&gt;encouraging simpler implementations&lt;/li&gt;
&lt;li&gt;reducing unnecessary abstractions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are situations where readability and maintainability often matter more than generating the maximum amount of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Avoid It?
&lt;/h3&gt;

&lt;p&gt;Ponytail may not add much value if you’re:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;learning programming from scratch&lt;/li&gt;
&lt;li&gt;writing small throwaway scripts&lt;/li&gt;
&lt;li&gt;experimenting with new ideas&lt;/li&gt;
&lt;li&gt;using AI only for quick code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, a standard AI assistant may already be sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Rails Example
&lt;/h3&gt;

&lt;p&gt;Suppose you ask your AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Optimize this ActiveRecord query.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without guidance, the AI might introduce several service objects, helper modules, and layers of abstraction.&lt;/p&gt;

&lt;p&gt;With a “keep it simple” philosophy, the AI is more likely to suggest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding the correct index&lt;/li&gt;
&lt;li&gt;using includes&lt;/li&gt;
&lt;li&gt;avoiding N+1 queries&lt;/li&gt;
&lt;li&gt;simplifying the ActiveRecord chain&lt;/li&gt;
&lt;li&gt;making a small, focused improvement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This often aligns well with how experienced Rails developers prefer to evolve applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently Asked Questions
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Is Ponytail another AI?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Ponytail write code?
&lt;/h3&gt;

&lt;p&gt;Not by itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail work without Claude or another AI model?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail replace Cursor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail replace VS Code?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Ponytail a code editor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail guarantee better code?
&lt;/h3&gt;

&lt;p&gt;No. It provides guidance, but you should always review AI-generated code before merging it into production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;AI-assisted development is evolving at an incredible pace. New models, editors, and coding agents appear regularly, and the surrounding ecosystem changes just as quickly.&lt;/p&gt;

&lt;p&gt;Ponytail is best thought of as &lt;strong&gt;a way to influence how an AI coding assistant approaches software development&lt;/strong&gt; , encouraging simpler, more maintainable solutions rather than acting as a new AI model or editor.&lt;/p&gt;

&lt;p&gt;Whether Ponytail becomes part of your daily workflow depends on your projects, your preferred tools, and how much you value AI-generated code that favors clarity over complexity.&lt;/p&gt;

&lt;p&gt;As with any AI tool, the most important skill isn’t learning a specific product — it’s understanding where that product fits into the broader development workflow.&lt;/p&gt;

&lt;p&gt;If this article introduced you to Ponytail for the first time, take a few minutes to explore its latest documentation, experiment with it in a small project, and decide whether its philosophy aligns with the way you like to build software.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Part 3: Optimistic Locking in Rails – Preventing Lost Updates Without Blocking Users</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:49:53 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/part-3-optimistic-locking-in-rails-preventing-lost-updates-without-blocking-users-hga</link>
      <guid>https://dev.to/yashika_vijayvargiya/part-3-optimistic-locking-in-rails-preventing-lost-updates-without-blocking-users-hga</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the previous article, we learned how Pessimistic Locking prevents concurrent modifications by locking database rows.&lt;/p&gt;

&lt;p&gt;While this guarantees data consistency, it also means other users may have to wait until the lock is released.&lt;/p&gt;

&lt;p&gt;But what if conflicts are rare?&lt;/p&gt;

&lt;p&gt;Should we still lock every row?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Optimistic Locking&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Instead of preventing concurrent updates, Optimistic Locking allows multiple users to edit the same record and detects conflicts only when they try to save their changes.&lt;/p&gt;

&lt;p&gt;It assumes that conflicts are unlikely, making it a great choice for many web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Optimistic Locking?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Optimistic Locking is a concurrency control strategy that allows multiple transactions to read and modify the same record without acquiring database locks.&lt;/p&gt;

&lt;p&gt;Before saving changes, Rails checks whether the record has been modified by another transaction.&lt;/p&gt;

&lt;p&gt;If it has, Rails raises an exception instead of silently overwriting the newer data.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;p&gt;"I believe no one else will modify this record. But before saving, I'll verify that assumption."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Do We Need Optimistic Locking?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Imagine an admin panel.&lt;/p&gt;

&lt;p&gt;Two administrators open the same product.&lt;/p&gt;

&lt;p&gt;Current Product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16
Price: ₹80,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin A changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price → ₹75,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin B changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title → iPhone 16 Pro

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

&lt;/div&gt;



&lt;p&gt;Without optimistic locking:&lt;/p&gt;

&lt;p&gt;Admin A saves.&lt;/p&gt;

&lt;p&gt;Database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16
Price: ₹75,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few seconds later...&lt;/p&gt;

&lt;p&gt;Admin B saves.&lt;/p&gt;

&lt;p&gt;Database becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16 Pro
Price: ₹80,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin A's update is lost.&lt;/p&gt;

&lt;p&gt;This is called a Lost Update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Rails Solves This&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rails provides built-in support for Optimistic Locking using a column named:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Whenever a record is updated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails increments lock_version&lt;/li&gt;
&lt;li&gt;Rails verifies the previous version before updating&lt;/li&gt;
&lt;li&gt;If the version has changed, Rails raises an exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Enabling Optimistic Locking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simply add a column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AddLockVersionToProducts &amp;lt; ActiveRecord::Migration[8.0]
  def change
    add_column :products,
               :lock_version,
               :integer,
               default: 0,
               null: false
  end
end

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

&lt;/div&gt;



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

&lt;p&gt;Rails automatically enables optimistic locking.&lt;/p&gt;

&lt;p&gt;No additional configuration is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Current row:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbgf0cga0wksw2gn0akgx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbgf0cga0wksw2gn0akgx.png" alt=" " width="671" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Admin A loads:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Admin B also loads:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Admin A updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product.price = 75000
product.save!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7l8pb32erm7b8u1tesqa.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7l8pb32erm7b8u1tesqa.png" alt=" " width="668" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;became&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Now Admin B tries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product.title = "iPhone Pro"

product.save!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails generates SQL similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE products SET title = 'iPhone Pro', lock_version = 2 WHERE id = 1 AND lock_version = 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the database contains:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Therefore:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Rails raises:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Timeline&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

Read Product

Version = 0

↓

Edit

↓

Save

Version becomes 1

----------------------------

Admin B

Read Product

Version = 0

↓

Edit

↓

Try Save

↓

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Happens Internally?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose current version:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Rails updates using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE products
SET
price = 100,
lock_version = 4
WHERE
id = 1
AND lock_version = 3;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another transaction already updated it:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;The WHERE condition fails.&lt;/p&gt;

&lt;p&gt;No row is updated.&lt;/p&gt;

&lt;p&gt;Rails knows someone modified the record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling the Exception&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typical implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;begin
  product.update!(product_params)

rescue ActiveRecord::StaleObjectError

  flash[:alert] =
    "This product was updated by another user. Please reload and try again."

  redirect_to edit_product_path(product)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of silently overwriting data, the user is informed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Example 1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CMS Article Editing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two editors modify the same article.&lt;/p&gt;

&lt;p&gt;Without optimistic locking:&lt;/p&gt;

&lt;p&gt;One editor overwrites another's work.&lt;/p&gt;

&lt;p&gt;With optimistic locking:&lt;/p&gt;

&lt;p&gt;Second editor sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"This article has changed since you opened it."

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real Production Example 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Admin Dashboard&lt;/p&gt;

&lt;p&gt;Inventory manager changes:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Manager B changes:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Instead of losing one update,&lt;/p&gt;

&lt;p&gt;Rails detects the conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Example 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User Profile&lt;/p&gt;

&lt;p&gt;Editing profile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Address&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conflicts are rare.&lt;/p&gt;

&lt;p&gt;Blocking users would hurt UX.&lt;/p&gt;

&lt;p&gt;Optimistic locking is a better fit.&lt;/p&gt;

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

&lt;p&gt;✅ No waiting&lt;/p&gt;

&lt;p&gt;✅ Better scalability&lt;/p&gt;

&lt;p&gt;✅ Better user experience&lt;/p&gt;

&lt;p&gt;✅ No database locks&lt;/p&gt;

&lt;p&gt;✅ Great for read-heavy applications&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;❌ Save may fail&lt;/p&gt;

&lt;p&gt;❌ Users must retry&lt;/p&gt;

&lt;p&gt;❌ Not suitable for financial systems&lt;/p&gt;

&lt;p&gt;❌ Doesn't prevent concurrent reads&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimistic vs Pessimistic Locking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8c5ck0j2o506c472onsh.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8c5ck0j2o506c472onsh.png" alt=" " width="521" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Database Locking in Ruby on Rails: Types, Examples, and Best Practices</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 15 Jul 2026 08:39:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</guid>
      <description>&lt;p&gt;Goal of this article: Build a strong foundation before learning pessimistic locking, optimistic locking, deadlocks, and isolation levels.&lt;/p&gt;

&lt;p&gt;When I started working with production Rails applications, I realized that most database problems were not caused by missing indexes but by concurrent requests modifying the same data. Understanding locking completely changed the way I design payment, inventory, and booking systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a lock?
&lt;/h3&gt;

&lt;p&gt;A lock is a mechanism used by PostgreSQL to protect data from being modified incorrectly by multiple transactions at the same time.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;p&gt;A lock tells other transactions: “Someone is currently working with this data. Wait your turn.”&lt;/p&gt;

&lt;p&gt;Imagine two people editing the same Google Doc at the same moment. Without coordination, one person’s changes could overwrite the other’s. Databases use locks to prevent similar problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do databases need locking?
&lt;/h3&gt;

&lt;p&gt;Suppose an e-commerce application has only 1 item left in inventory.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qgwp5xtkezw1ksm1r1g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qgwp5xtkezw1ksm1r1g.png" width="796" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without locking, both requests may read:&lt;/p&gt;

&lt;p&gt;Both transactions believe the product is available and both place an order.&lt;/p&gt;

&lt;p&gt;Result:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnf43sdr7w3tpon0kj12.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnf43sdr7w3tpon0kj12.png" width="560" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is called a race condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a race condition?
&lt;/h3&gt;

&lt;p&gt;A race condition occurs when the final result depends on the timing of concurrent operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;If two requests execute this code simultaneously, both may read the same inventory value before either update occurs.&lt;/p&gt;

&lt;p&gt;This is the fundamental problem locking is designed to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions: The Foundation of Locking
&lt;/h3&gt;

&lt;p&gt;Locks exist inside database transactions.&lt;/p&gt;

&lt;p&gt;A transaction guarantees that all operations inside the block either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Succeed together (COMMIT)&lt;/li&gt;
&lt;li&gt;Fail together (ROLLBACK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locks are typically acquired when the transaction begins modifying or explicitly locking rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Handles Concurrent Transactions
&lt;/h3&gt;

&lt;p&gt;Consider two transactions.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo10qhhg5pghoefccet2.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo10qhhg5pghoefccet2.png" width="768" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL automatically prevents both transactions from modifying the same row simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  MVCC: Why SELECT Usually Doesn’t Block
&lt;/h3&gt;

&lt;p&gt;One of PostgreSQL’s most important features is MVCC (Multi-Version Concurrency Control).&lt;/p&gt;

&lt;p&gt;Instead of blocking readers, PostgreSQL creates multiple versions of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Transaction A updates a product but has not committed yet.&lt;/p&gt;

&lt;p&gt;At the same time, Transaction B runs:&lt;/p&gt;

&lt;p&gt;Transaction B usually does not wait. It reads the previous committed version of the row.&lt;/p&gt;

&lt;p&gt;This is why PostgreSQL can handle many readers efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Concurrency Problems
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. Lost Update
&lt;/h3&gt;

&lt;p&gt;Very common&lt;/p&gt;

&lt;p&gt;Problem: Two users overwrite each other’s changes.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgzuhxyb15lab2pijx21.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgzuhxyb15lab2pijx21.png" width="775" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Correct result should be: $30&lt;/p&gt;

&lt;p&gt;Actual result: $50&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dirty Read
&lt;/h3&gt;

&lt;p&gt;Usually prevented in PostgreSQL&lt;/p&gt;

&lt;p&gt;Reading data that another transaction has modified but not committed.&lt;/p&gt;

&lt;p&gt;PostgreSQL’s default isolation level (Read Committed) prevents this.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Non-repeatable Read
&lt;/h3&gt;

&lt;p&gt;Possible in Read Committed&lt;/p&gt;

&lt;p&gt;You read the same row twice and get different values because another transaction committed between the reads.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Phantom Read
&lt;/h3&gt;

&lt;p&gt;Isolation-level issue&lt;/p&gt;

&lt;p&gt;A query returns a different set of rows the second time it runs because another transaction inserted matching records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rails Example: Preventing Double Booking
&lt;/h3&gt;

&lt;p&gt;Unsafe code:&lt;/p&gt;

&lt;p&gt;Safe code using locking:&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;generates:&lt;/p&gt;

&lt;p&gt;This acquires a row-level lock so only one transaction can modify that event record at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Timeline
&lt;/h3&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm0u08p3f47bbrha2tci.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm0u08p3f47bbrha2tci.png" width="773" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Insight: PostgreSQL Uses Multiple Lock Types
&lt;/h3&gt;

&lt;p&gt;Many beginners think there is only one lock.&lt;/p&gt;

&lt;p&gt;In reality PostgreSQL has:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F772dofyate0sbg25guxm.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F772dofyate0sbg25guxm.png" width="774" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll explore these deeply in later parts of the series.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to See Locks in Production
&lt;/h3&gt;

&lt;p&gt;PostgreSQL exposes lock information through pg_locks.&lt;/p&gt;

&lt;p&gt;A more useful query:&lt;/p&gt;

&lt;p&gt;This helps identify which transaction is holding a lock and which transaction is waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;p&gt;Locks prevent race conditions and data corruption.&lt;/p&gt;

&lt;p&gt;Locks exist inside transactions.&lt;/p&gt;

&lt;p&gt;PostgreSQL uses MVCC so readers usually do not block writers.&lt;/p&gt;

&lt;p&gt;Row locks protect individual records.&lt;/p&gt;

&lt;p&gt;Table locks protect entire tables.&lt;/p&gt;

&lt;p&gt;FOR UPDATE is the foundation of pessimistic locking in Rails.&lt;/p&gt;

&lt;p&gt;Most production concurrency bugs are caused by missing transactions or missing locks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interview Question
&lt;/h3&gt;

&lt;p&gt;Why does PostgreSQL allow many SELECT queries to run without blocking each other?&lt;/p&gt;

&lt;p&gt;Answer: PostgreSQL uses MVCC (Multi-Version Concurrency Control). Instead of blocking readers, it keeps multiple versions of rows so a SELECT query can read the last committed version while another transaction is updating the row. This greatly improves concurrency and is one of PostgreSQL’s biggest advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Article in the Series
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Pessimistic Locking in Rails
&lt;/h3&gt;

&lt;p&gt;We will dive into lock, with_lock, FOR UPDATE, NOWAIT, SKIP LOCKED, and real production examples such as inventory reservation and payment processing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/understanding-database-locking-in-rails-postgresql-part-1" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 13, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>howto</category>
      <category>rails</category>
      <category>locking</category>
    </item>
    <item>
      <title>PostgreSQL Index Types Explained with Real Rails Examples</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 08:00:26 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</link>
      <guid>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</guid>
      <description>&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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" 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%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" width="1024" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part-1 &lt;a href="https://railswithyashika.hashnode.dev/understanding-indexes-before-learning-index-types" rel="noopener noreferrer"&gt;Understanding Indexes before learning Index Types&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Now that we understand what indexes are and why they matter, let’s explore the different types of indexes available in PostgreSQL and when to use them in Rails applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Single Column Index
&lt;/h3&gt;

&lt;p&gt;The simplest and most commonly used index.&lt;/p&gt;
&lt;h3&gt;
  
  
  Query
&lt;/h3&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;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  SQL Generated
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;index_users_on_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Email lookups&lt;/li&gt;
&lt;li&gt;SKU lookups&lt;/li&gt;
&lt;li&gt;UUID searches&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&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;sku: &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;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Without an index, PostgreSQL scans the entire table.&lt;/p&gt;

&lt;p&gt;With an index, PostgreSQL can jump directly to the matching row.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Unique Index
&lt;/h3&gt;

&lt;p&gt;A unique index improves lookup performance while also preventing duplicate values.&lt;/p&gt;
&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&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="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  What It Solves
&lt;/h3&gt;

&lt;p&gt;Prevents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john@example.com
john@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Usernames&lt;/li&gt;
&lt;li&gt;External IDs&lt;/li&gt;
&lt;li&gt;API tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rails Validation vs Database Constraint
&lt;/h3&gt;

&lt;p&gt;Many developers 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;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not enough.&lt;/p&gt;

&lt;p&gt;Always add a database unique index because validations can be bypassed during race conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Composite (Multi-column) Index
&lt;/h3&gt;

&lt;p&gt;Used when queries filter using multiple columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&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="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important Rule
&lt;/h3&gt;

&lt;p&gt;Index order matters.&lt;br&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="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works efficiently for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&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;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But not for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;

&lt;p&gt;In e-commerce:&lt;br&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;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Partial Index
&lt;/h3&gt;

&lt;p&gt;Indexes only a subset of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&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="ss"&gt;where: &lt;/span&gt;&lt;span class="s2"&gt;"active = true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example Query
&lt;/h3&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use It?
&lt;/h3&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 million users
9 million inactive 
1 million active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an index only for active users makes the index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller&lt;/li&gt;
&lt;li&gt;Faster&lt;/li&gt;
&lt;li&gt;Less memory intensive&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Uses
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Soft deletes&lt;/li&gt;
&lt;li&gt;Active records&lt;/li&gt;
&lt;li&gt;Published content&lt;/li&gt;
&lt;li&gt;Pending jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Index with Order
&lt;/h3&gt;

&lt;p&gt;Optimizes sorting operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;created_at: :desc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;br&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;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;News feeds&lt;/li&gt;
&lt;li&gt;Activity logs&lt;/li&gt;
&lt;li&gt;Product listings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. GIN Index
&lt;/h3&gt;

&lt;p&gt;GIN stands for Generalized Inverted Index.&lt;/p&gt;

&lt;p&gt;Used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSONB&lt;/li&gt;
&lt;li&gt;Arrays&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSONB Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:preferences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;preferences&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{"theme":"dark"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Rails Example
&lt;/h3&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"preferences @&amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;theme: &lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without GIN indexes, JSONB searches become slow as data grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Full Text Search Index
&lt;/h3&gt;

&lt;p&gt;PostgreSQL can act as a search engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"to_tsvector('english', content)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&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_articles_on_content_search"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rails indexing'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Useful For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Blogs&lt;/li&gt;
&lt;li&gt;Documentation sites&lt;/li&gt;
&lt;li&gt;Product search&lt;/li&gt;
&lt;li&gt;Knowledge bases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Concurrent Index
&lt;/h3&gt;

&lt;p&gt;One of the most important production techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Running:&lt;br&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;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on a huge table can lock writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&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;AddIndexToUsersEmail&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;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="n"&gt;disable_ddl_transaction!&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;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&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="ss"&gt;algorithm: :concurrently&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;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No downtime&lt;/li&gt;
&lt;li&gt;No table lock&lt;/li&gt;
&lt;li&gt;Safe for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real World
&lt;/h3&gt;

&lt;p&gt;Never add indexes to large production tables without considering concurrent creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Expression Index
&lt;/h3&gt;

&lt;p&gt;Indexes a calculated value instead of a column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"LOWER(email) = ?"&lt;/span&gt;&lt;span class="p"&gt;,&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;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"LOWER(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_users_on_lower_email"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Supports fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case-insensitive searches&lt;/li&gt;
&lt;li&gt;Date transformations&lt;/li&gt;
&lt;li&gt;String manipulations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Covering Index (INCLUDE)
&lt;/h3&gt;

&lt;p&gt;Introduced to reduce table lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&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="ss"&gt;include: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Useful?
&lt;/h3&gt;

&lt;p&gt;Query:&lt;br&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;Order&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;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL can answer directly from the index.&lt;/p&gt;

&lt;p&gt;This is called an &lt;strong&gt;Index Only Scan&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fewer disk reads&lt;/li&gt;
&lt;li&gt;Faster queries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  11. BRIN Index
&lt;/h3&gt;

&lt;p&gt;BRIN stands for Block Range Index.&lt;/p&gt;

&lt;p&gt;Designed for huge tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :brin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Analytics data&lt;/li&gt;
&lt;li&gt;Audit records&lt;/li&gt;
&lt;li&gt;Time-series events&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Instead of storing every value, BRIN stores summaries for ranges of pages.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely small indexes&lt;/li&gt;
&lt;li&gt;Very low maintenance overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 million events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A BRIN index may be only a few MBs compared to hundreds of MBs for a B-tree index.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Hash Index
&lt;/h3&gt;

&lt;p&gt;Optimized for equality lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&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="ss"&gt;using: :hash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitation
&lt;/h3&gt;

&lt;p&gt;Does not support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;
&amp;gt;
BETWEEN
ORDER BY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because B-tree indexes support more operations, Hash indexes are rarely used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Index Should You Choose?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;Indexes are not about adding them everywhere.&lt;/p&gt;

&lt;p&gt;A good database engineer first identifies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slow queries&lt;/li&gt;
&lt;li&gt;Query patterns&lt;/li&gt;
&lt;li&gt;Filter columns&lt;/li&gt;
&lt;li&gt;Sort columns&lt;/li&gt;
&lt;li&gt;Join columns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then chooses the index that matches the workload.&lt;/p&gt;

&lt;p&gt;Always verify improvements using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and remember that every index speeds up reads but adds overhead to writes. The best indexing strategy is the one that balances both.&lt;/p&gt;

&lt;p&gt;Concurrent Indexes: &lt;a href="https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production" rel="noopener noreferrer"&gt;https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/postgresql-index-types-explained-with-real-rails-examples" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 1, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>performance</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Understanding Indexes Before Learning Index Types</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 01 Jul 2026 07:59:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</guid>
      <description>&lt;p&gt;Before diving into different types of indexes, let’s understand why indexes are needed.&lt;/p&gt;

&lt;p&gt;Imagine a library with 100,000 books.&lt;/p&gt;

&lt;p&gt;If someone asks for a book named &lt;em&gt;“Clean Code”&lt;/em&gt;, there are two ways to find it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Without an Index
&lt;/h3&gt;

&lt;p&gt;You start from the first shelf and check every book one by one until you find the required book.&lt;/p&gt;

&lt;p&gt;This approach is similar to a &lt;strong&gt;Sequential Scan&lt;/strong&gt; in PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Clean Code'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL may need to scan every row in the table to find matching records.&lt;/p&gt;

&lt;p&gt;As the table grows from thousands to millions of rows, query performance degrades significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an Index
&lt;/h3&gt;

&lt;p&gt;Now imagine the library has a catalog that stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Clean Code → Shelf A12
The Pragmatic Programmer → Shelf B03
Refactoring → Shelf C08
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of checking every book, you first look at the catalog and immediately jump to the correct shelf.&lt;/p&gt;

&lt;p&gt;This catalog is similar to a database index.&lt;/p&gt;

&lt;p&gt;An index stores selected column values in a structure that allows PostgreSQL to locate rows much faster than scanning the entire table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Index Every Column?
&lt;/h3&gt;

&lt;p&gt;A common beginner question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If indexes make queries faster, why not create indexes everywhere?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indexes improve read performance, but they come with a cost.&lt;/p&gt;

&lt;p&gt;Whenever data changes, PostgreSQL must update both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The table data&lt;/li&gt;
&lt;li&gt;Every related index&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&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;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the users table has 10 indexes, PostgreSQL must update all 10 indexes during the insert operation.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More storage usage&lt;/li&gt;
&lt;li&gt;Slower INSERT operations&lt;/li&gt;
&lt;li&gt;Slower UPDATE operations&lt;/li&gt;
&lt;li&gt;Slower DELETE operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of this trade-off, indexes should be added only for queries that are frequently executed and benefit from faster lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Uses an Index
&lt;/h3&gt;

&lt;p&gt;Suppose we have:&lt;br&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;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using index_users_on_email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database can jump directly to matching records instead of examining every row.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Rule
&lt;/h3&gt;

&lt;p&gt;Create an index when a column is frequently used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WHERE clauses&lt;/li&gt;
&lt;li&gt;JOIN conditions&lt;/li&gt;
&lt;li&gt;ORDER BY clauses&lt;/li&gt;
&lt;li&gt;GROUP BY clauses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&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;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are strong candidates for indexing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Verify an Index Is Being Used
&lt;/h3&gt;

&lt;p&gt;Never assume PostgreSQL is using your index.&lt;/p&gt;

&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This confirms PostgreSQL is benefiting from the index.&lt;/p&gt;

&lt;p&gt;Understanding these fundamentals makes it much easier to choose the right indexing strategy, whether it is a simple B-Tree index, a composite index, a GIN index for JSONB data, or a BRIN index for massive datasets.&lt;/p&gt;

</description>
      <category>indexes</category>
      <category>rails</category>
      <category>database</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
