<?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>Rate Limiting in Rails 8.1: Protect Your APIs Without Rack::Attack</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Mon, 02 Nov 2026 10:41:02 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/rate-limiting-in-rails-81-protect-your-apis-without-rackattack-8ij</link>
      <guid>https://dev.to/yashika_vijayvargiya/rate-limiting-in-rails-81-protect-your-apis-without-rackattack-8ij</guid>
      <description>&lt;p&gt;Have you ever wondered what happens when someone sends hundreds of requests to your login endpoint within a few seconds?&lt;/p&gt;

&lt;p&gt;Without rate limiting, your application could face unnecessary database queries, excessive server load, brute-force login attempts, or API abuse.&lt;/p&gt;

&lt;p&gt;Rate limiting helps control how frequently a client can access an endpoint within a specified time window.&lt;/p&gt;

&lt;p&gt;The good news is that Rails provides a built-in rate_limit method, so you don't always need an additional gem to implement basic rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails 8.1 also introduces a useful improvement: the&lt;/strong&gt; scope: &lt;strong&gt;option, which allows multiple controllers to share the same rate-limit counter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s understand how it works with practical examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What Is Rate Limiting?
&lt;/h3&gt;

&lt;p&gt;Rate limiting is a technique used to restrict the number of requests a client can make within a specific period.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Allow 5 login attempts within 3 minutes.&lt;/li&gt;
&lt;li&gt;Allow 100 API requests per minute.&lt;/li&gt;
&lt;li&gt;Allow 10 password reset requests within 15 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a client exceeds the configured limit, the application rejects additional requests.&lt;/p&gt;

&lt;p&gt;Usually, the server responds with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 429 Too Many Requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps protect application resources and reduces abuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rails Built-in Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Rails provides the rate_limit class method in Action Controller.&lt;/p&gt;

&lt;p&gt;You can configure it directly inside a controller without installing an additional gem.&lt;/p&gt;

&lt;p&gt;Let’s consider a login endpoint.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: :create&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# Login authentication logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s understand the configuration:&lt;/p&gt;

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

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

&lt;p&gt;to: 5&lt;/p&gt;

&lt;p&gt;Maximum 5 requests&lt;/p&gt;

&lt;p&gt;within: 3.minutes&lt;/p&gt;

&lt;p&gt;Within a 3-minute window&lt;/p&gt;

&lt;p&gt;only: :create&lt;/p&gt;

&lt;p&gt;Apply the limit only to the create action&lt;/p&gt;

&lt;p&gt;By default, Rails identifies requests using the client’s IP address.&lt;/p&gt;

&lt;p&gt;When the limit is exceeded, Rails raises ActionController::TooManyRequests, which Action Dispatch handles by returning HTTP 429.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Customize the Rate Limit Response
&lt;/h3&gt;

&lt;p&gt;The default response might not be sufficient for an API application.&lt;/p&gt;

&lt;p&gt;You can customize the response using the with: 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;only: :create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="s2"&gt;"Too many login attempts. Please try again later."&lt;/span&gt;
               &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;status: :too_many_requests&lt;/span&gt;
             &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# Login authentication logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when a client exceeds the configured limit, the API returns a JSON error response.&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 json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Too many login attempts. Please try again later."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With HTTP status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;429 Too Many Requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly useful when building APIs consumed by React, mobile applications, or other external clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rate Limit Based on User Instead of IP
&lt;/h3&gt;

&lt;p&gt;By default, Rails applies rate limiting based on the request IP address.&lt;/p&gt;

&lt;p&gt;But what if you want to apply limits per authenticated user?&lt;/p&gt;

&lt;p&gt;You can customize the identifier using the by: 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::BaseController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;by: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remote_ip&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticated users are identified by their user ID.&lt;/li&gt;
&lt;li&gt;Unauthenticated requests are identified by their IP address.&lt;/li&gt;
&lt;li&gt;Each identifier gets its own rate-limit counter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is useful when you want to provide individual API limits to authenticated users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; If your application runs behind a proxy or load balancer, configure trusted proxy handling correctly. Otherwise, IP-based limits may not identify clients as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What’s New in Rails 8.1? The scope: Option
&lt;/h3&gt;

&lt;p&gt;This is one of the most useful improvements to the built-in rate-limiting functionality.&lt;/p&gt;

&lt;p&gt;Before Rails 8.1, rate-limit counters were scoped to individual controller paths by default.&lt;/p&gt;

&lt;p&gt;Consider an API application with two controllers:&lt;br&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;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PostsController&lt;/span&gt;
&lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UsersController&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose both controllers have the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rate_limit to: 100, within: 1.minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, these controllers maintain separate counters.&lt;/p&gt;

&lt;p&gt;This means a client could make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100 requests to the posts endpoint.&lt;/li&gt;
&lt;li&gt;100 requests to the users endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s 200 requests in total.&lt;/p&gt;

&lt;p&gt;What if you want both controllers to share one common limit?&lt;/p&gt;

&lt;p&gt;Rails 8.1 introduces the scope: option for this purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Shared rate limit across controllers
&lt;/h3&gt;

&lt;p&gt;Create a common API controller:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="s2"&gt;"public_api"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now inherit from 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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApiController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both controllers share the same rate-limit scope.&lt;/p&gt;

&lt;p&gt;If a client makes 60 requests to the posts endpoint, only 40 requests remain for the users endpoint during the same rate-limit window.&lt;/p&gt;

&lt;p&gt;The shared counter uses the same scope and client identifier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is this useful?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine an application exposing 20 public API endpoints. Instead of allowing 100 requests per endpoint, you can enforce a common limit across the entire API.&lt;/p&gt;

&lt;p&gt;This gives you better control over overall API usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Multiple Rate Limits on the Same Controller
&lt;/h3&gt;

&lt;p&gt;Sometimes one rate limit isn’t enough.&lt;/p&gt;

&lt;p&gt;For example, you might want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A short-term limit of 3 requests every 2 seconds.&lt;/li&gt;
&lt;li&gt;A longer-term limit of 10 requests every 5 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rails supports multiple rate limits on the same controller.&lt;/p&gt;

&lt;p&gt;Use the name: option to distinguish them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&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;"short_term"&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"long_term"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# Login logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each limit maintains its own counter.&lt;/p&gt;

&lt;p&gt;This is useful when you want to prevent both sudden bursts of traffic and repeated requests over a longer period.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Where Does Rails Store Rate-Limit Counters?
&lt;/h3&gt;

&lt;p&gt;An important question is: How does Rails remember how many requests a client has already made?&lt;/p&gt;

&lt;p&gt;Rails uses an ActiveSupport::Cache store to maintain rate-limit counters.&lt;/p&gt;

&lt;p&gt;By default, it uses the controller cache store, which falls back to the application’s configured cache store.&lt;/p&gt;

&lt;p&gt;For a production application running on multiple servers, using a shared Redis cache can be useful.&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 ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="no"&gt;RATE_LIMIT_STORE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RedisCacheStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"REDIS_URL"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;rate_limit&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;within: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="ss"&gt;store: &lt;/span&gt;&lt;span class="no"&gt;RATE_LIMIT_STORE&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the rate-limit counters are stored in Redis.&lt;/p&gt;

&lt;p&gt;Why does this matter?&lt;/p&gt;

&lt;p&gt;Imagine your application runs on three application servers:&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
                   |
          -------------------
          | | |
        Server 1 Server 2 Server 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If each server maintains its own independent counters, a client might bypass the intended limit by sending requests to different servers.&lt;/p&gt;

&lt;p&gt;Using a shared Redis cache allows all three servers to access the same counters.&lt;/p&gt;

&lt;p&gt;Make sure Redis is configured with appropriate availability, memory, and eviction settings for your production workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Rails Rate Limiting vs Rack::Attack
&lt;/h3&gt;

&lt;p&gt;Before Rails introduced built-in rate limiting, developers commonly used gems such as Rack::Attack.&lt;/p&gt;

&lt;p&gt;Does Rails’ built-in functionality mean you no longer need Rack::Attack?&lt;/p&gt;

&lt;p&gt;Not necessarily.&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%2Fto64usbx3717am91evx0.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%2Fto64usbx3717am91evx0.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For straightforward controller-level rate limiting, Rails’ built-in functionality is often sufficient.&lt;/p&gt;

&lt;p&gt;If you need advanced request filtering, blocklists, safelists, or more complex throttling rules, Rack::Attack is still worth considering.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. When Should You Use Rails Built-in Rate Limiting?
&lt;/h3&gt;

&lt;p&gt;Here are some practical use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Login endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prevent repeated login attempts that could indicate brute-force activity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password reset endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid excessive password reset requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public APIs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Control how frequently clients can access your API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registration endpoints&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reduce automated account creation attempts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expensive operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Protect endpoints that perform costly database queries or external API calls.&lt;/p&gt;

&lt;p&gt;Remember that rate limiting is only one layer of application protection. You should also implement authentication, authorization, input validation, monitoring, and appropriate infrastructure-level protections.&lt;/p&gt;

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

&lt;p&gt;Rails makes basic rate limiting easier by providing the rate_limit method directly in Action Controller.&lt;/p&gt;

&lt;p&gt;With Rails 8.1, the new scope: option makes it possible to share rate-limit counters across multiple controllers.&lt;/p&gt;

&lt;p&gt;The key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use rate_limit to restrict requests within a time window.&lt;/li&gt;
&lt;li&gt;Use by: to customize client identification.&lt;/li&gt;
&lt;li&gt;Use with: to customize the response.&lt;/li&gt;
&lt;li&gt;Use scope: to share limits across controllers.&lt;/li&gt;
&lt;li&gt;Use store: to configure a shared cache such as Redis.&lt;/li&gt;
&lt;li&gt;Use name: when defining multiple limits on one controller.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t always need an additional gem to protect your Rails endpoints from excessive requests.&lt;/p&gt;

&lt;p&gt;Sometimes, the framework already provides the functionality you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you used Rails’ built-in rate limiting, or do you prefer Rack::Attack? Share your experience in the comments!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/v8.1.3/classes/ActionController/RateLimiting/ClassMethods.html" rel="noopener noreferrer"&gt;Rails 8.1 Action Controller Rate Limiting API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rubyrubyrubyruby.dev/rails/8.1/actionpack/CHANGELOG_md.html" rel="noopener noreferrer"&gt;Rails 8.1 Action Pack Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rack/rack-attack" rel="noopener noreferrer"&gt;Rack::Attack GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>programming</category>
      <category>ratelimiting</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Rails 8.1: Using only_columns to Control Which Database Columns Active Record Uses</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Mon, 02 Nov 2026 09:08:17 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/rails-81-using-onlycolumns-to-control-which-database-columns-active-record-uses-1nkn</link>
      <guid>https://dev.to/yashika_vijayvargiya/rails-81-using-onlycolumns-to-control-which-database-columns-active-record-uses-1nkn</guid>
      <description>&lt;p&gt;When working with legacy databases or large Rails applications, a model may have more database columns than the application actually needs.&lt;/p&gt;

&lt;p&gt;Some columns might belong to an older feature, a third-party integration, or a database structure shared by multiple applications.&lt;/p&gt;

&lt;p&gt;Traditionally, Rails developers could use ignored_columns to tell Active Record which columns to ignore. With &lt;strong&gt;Rails 8.1,&lt;/strong&gt; only_columns &lt;strong&gt;provides the opposite approach: explicitly list the columns a model should use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s understand how it works, how to test it in the Rails console, and when it can be useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What Is only_columns in Rails 8.1?
&lt;/h3&gt;

&lt;p&gt;ActiveRecord::Base.only_columns allows you to specify the database columns that a model should recognize and use.&lt;/p&gt;

&lt;p&gt;Instead of listing the columns you want to ignore, you list the columns you want Active Record to use.&lt;/p&gt;

&lt;p&gt;Consider a users table with these columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id
name
email
phone
address
legacy_reference
external_customer_id
created_at
updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose your application only needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;li&gt;created_at&lt;/li&gt;
&lt;li&gt;updated_at&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can explicitly define that list in your model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How to Use only_columns
&lt;/h3&gt;

&lt;p&gt;Let’s use a User model as an example.&lt;/p&gt;

&lt;p&gt;app/models/user.rb&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;only_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[
    id
    name
    email
    created_at
    updated_at
  ]&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, Active Record uses the configured column list when defining model attributes and constructing SQL queries.&lt;/p&gt;

&lt;p&gt;The database schema itself remains unchanged. The phone, address, legacy_reference, and external_customer_id columns still exist in the database.&lt;/p&gt;

&lt;p&gt;The difference is that this model is configured to work with an explicitly defined subset of those columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Test It in the Rails Console
&lt;/h3&gt;

&lt;p&gt;One of the easiest ways to understand this feature is to inspect the model directly.&lt;/p&gt;

&lt;p&gt;Start the Rails console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the configured columns:&lt;br&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;only_columns&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also inspect the model’s recognized column names:&lt;br&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;column_names&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result reflects the columns Active Record recognizes for this model, subject to the configured column list.&lt;/p&gt;

&lt;p&gt;To inspect the attributes of a new 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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attribute list should reflect the configured columns rather than every column in the database table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is useful when debugging a model in the Rails console:&lt;/strong&gt; you can check which columns the model is configured to use without changing the underlying database schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Why Is This Useful?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  A. Working with legacy databases
&lt;/h3&gt;

&lt;p&gt;Imagine you’re maintaining an application that has been running for several years.&lt;/p&gt;

&lt;p&gt;Its database contains columns left behind by older features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users
-----
id
name
email
old_subscription_id
legacy_status
deprecated_reference
created_at
updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your current application only needs the user’s basic information.&lt;/p&gt;

&lt;p&gt;With only_columns, you can explicitly declare the columns that the model should use.&lt;/p&gt;

&lt;p&gt;This makes the intended model schema easier to understand and can reduce accidental dependencies on legacy columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Working with shared database schemas
&lt;/h3&gt;

&lt;p&gt;Some applications share a database with another service or application.&lt;/p&gt;

&lt;p&gt;For example, your Rails application might use a customers table that also contains columns maintained by an external integration.&lt;/p&gt;

&lt;p&gt;You may not want your application model to depend on every column in that shared table.&lt;/p&gt;

&lt;p&gt;An explicit column list helps document the subset that your application expects to use.&lt;/p&gt;

&lt;p&gt;Remember, this does not prevent other applications or direct SQL queries from accessing the remaining database columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Making schema changes safer
&lt;/h3&gt;

&lt;p&gt;Database changes often require careful coordination between deployments.&lt;/p&gt;

&lt;p&gt;Suppose you’re planning to remove an obsolete column.&lt;/p&gt;

&lt;p&gt;Rather than immediately removing the column from the database, you can first update the application so its model no longer depends on it.&lt;/p&gt;

&lt;p&gt;A possible rollout is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the columns the application actually needs.&lt;/li&gt;
&lt;li&gt;Configure only_columns to list those columns.&lt;/li&gt;
&lt;li&gt;Deploy the application and verify that it no longer depends on the obsolete column.&lt;/li&gt;
&lt;li&gt;Check other applications, background jobs, reports, and database queries for dependencies.&lt;/li&gt;
&lt;li&gt;Remove the obsolete column in a subsequent deployment when it is safe to do so.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach can make a schema migration easier to coordinate, especially when multiple application versions or services share a database.&lt;/p&gt;

&lt;p&gt;However, only_columns does not automatically make a migration safe. You still need to verify that no other code depends on the column.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. How Is only_columns Different from ignored_columns?
&lt;/h3&gt;

&lt;p&gt;Rails already provides ignored_columns, so why introduce another option?&lt;/p&gt;

&lt;p&gt;The difference is how you express your intention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using ignored_columns
&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignored_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[
    legacy_reference
    deprecated_reference
  ]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Active Record to ignore the listed columns.&lt;/p&gt;

&lt;p&gt;It’s convenient when most columns remain relevant and only a few need to be excluded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using only_columns
&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;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;only_columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;%w[
    id
    name
    email
    created_at
    updated_at
  ]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This explicitly lists the columns the model should use.&lt;/p&gt;

&lt;p&gt;It’s convenient when you want to define a known subset rather than maintain a list of columns to exclude.&lt;/p&gt;

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

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

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

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

&lt;p&gt;List columns to ignore&lt;/p&gt;

&lt;p&gt;List columns to use&lt;/p&gt;

&lt;p&gt;Best suited for&lt;/p&gt;

&lt;p&gt;Excluding a small number of columns&lt;/p&gt;

&lt;p&gt;Explicitly defining the model’s column set&lt;/p&gt;

&lt;p&gt;Database schema&lt;/p&gt;

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

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

&lt;p&gt;Can be combined with the other option?&lt;/p&gt;

&lt;p&gt;No; use one approach per model&lt;/p&gt;

&lt;p&gt;No; use one approach per model&lt;/p&gt;

&lt;p&gt;Choose the approach that best communicates your application’s intent.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Is only_columns a Security Feature?
&lt;/h3&gt;

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

&lt;p&gt;Although only_columns restricts the columns Active Record uses for a model, &lt;strong&gt;it should not be treated as a security or authorization mechanism&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, suppose your users table contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id
name
email
admin
password_digest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring a model to use only id, name, and email does not replace proper authorization, access control, or protection of sensitive information.&lt;/p&gt;

&lt;p&gt;You should still implement appropriate authorization policies, validate permitted input, and protect sensitive attributes.&lt;/p&gt;

&lt;p&gt;Use only_columns to control the model's expected database columns, not to enforce who can access data.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Things to Keep in Mind
&lt;/h3&gt;

&lt;p&gt;Before adopting only_columns, consider the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Associations:&lt;/strong&gt; Verify that the columns needed for association loading and joins are included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary keys:&lt;/strong&gt; Include the primary key or all required composite primary key columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps:&lt;/strong&gt; Include created_at and updated_at if the model relies on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background jobs and callbacks:&lt;/strong&gt; Check whether callbacks or other application code depend on excluded attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema changes:&lt;/strong&gt; Confirm that other applications and queries no longer depend on a column before removing it from the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rails version:&lt;/strong&gt; Use a patched Rails release. Rails 8.1.4 includes a fix ensuring that only_columns is respected when constructing the SELECT clause. Earlier Rails 8.1 versions may not handle this correctly in all queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, test the behavior with your actual model and associations before applying it to production.&lt;/p&gt;

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

&lt;p&gt;Rails 8.1’s only_columns provides an explicit way to tell Active Record which database columns a model should use.&lt;/p&gt;

&lt;p&gt;It can be useful when working with legacy tables, shared schemas, and staged database changes.&lt;/p&gt;

&lt;p&gt;The main takeaway is simple:&lt;/p&gt;

&lt;p&gt;ignored_columns &lt;strong&gt;tells Rails what to leave out.&lt;/strong&gt; only_columns &lt;strong&gt;tells Rails what to work with.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you maintain Rails applications with evolving database schemas, this is a feature worth understanding and testing.&lt;/p&gt;

&lt;p&gt;For more details, see the &lt;a href="https://guides.rubyonrails.org/8_1_release_notes.html" rel="noopener noreferrer"&gt;Rails 8.1 Release Notes&lt;/a&gt; and the &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html" rel="noopener noreferrer"&gt;Active Record ModelSchema API&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>api</category>
      <category>ratelimiting</category>
      <category>programming</category>
    </item>
    <item>
      <title>When to Choose Optimistic vs. Pessimistic Locking in Ruby on Rails</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:35:59 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/when-to-choose-optimistic-vs-pessimistic-locking-in-ruby-on-rails-5a57</link>
      <guid>https://dev.to/yashika_vijayvargiya/when-to-choose-optimistic-vs-pessimistic-locking-in-ruby-on-rails-5a57</guid>
      <description>&lt;p&gt;Not every concurrent database update needs the same locking strategy.&lt;/p&gt;

&lt;p&gt;Imagine two users trying to book the last available seat on a flight. Both see that the seat is available and click “Book” almost simultaneously. Without proper concurrency control, both requests might attempt to reserve the same seat.&lt;/p&gt;

&lt;p&gt;Ruby on Rails provides two common locking strategies to handle these situations: &lt;strong&gt;optimistic locking&lt;/strong&gt; and &lt;strong&gt;pessimistic locking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But the important question isn’t just how they work. It’s &lt;strong&gt;when should you choose one over the other?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s explore the differences through practical Rails examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. When Should You Choose Optimistic Locking?
&lt;/h3&gt;

&lt;p&gt;Choose optimistic locking when conflicts are relatively rare and you want to detect conflicting updates without locking a database record in advance.&lt;/p&gt;

&lt;p&gt;Rails supports optimistic locking using a lock_version column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-world example: Editing a product
&lt;/h3&gt;

&lt;p&gt;Imagine two administrators editing the same product in an e-commerce application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Admin A opens the product and sees its price as ₹500.&lt;/li&gt;
&lt;li&gt;Admin B opens the same product and sees its price as ₹500.&lt;/li&gt;
&lt;li&gt;Admin A changes the price to ₹550 and saves.&lt;/li&gt;
&lt;li&gt;Admin B changes the price to ₹600 and attempts to save.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without concurrency control, Admin B could overwrite Admin A’s update without realizing it.&lt;/p&gt;

&lt;p&gt;With optimistic locking, Rails detects that the record has changed since Admin B loaded it and raises ActiveRecord::StaleObjectError.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation in Rails
&lt;/h3&gt;

&lt;p&gt;First, add a lock_version column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddLockVersionToProducts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&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;:lock_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="ss"&gt;:integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="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;Rails automatically uses this column for optimistic locking.&lt;br&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;product_a&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;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;product_b&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;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;product_a&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="mi"&gt;550&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;product_b&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="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Raises ActiveRecord::StaleObjectError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second update fails because the record’s version has changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose optimistic locking when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Multiple users may edit the same record, but simultaneous updates are uncommon.&lt;/li&gt;
&lt;li&gt;Users spend time editing forms before saving.&lt;/li&gt;
&lt;li&gt;You want to detect stale updates instead of blocking other transactions.&lt;/li&gt;
&lt;li&gt;Your application can ask users to reload or resolve conflicting changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical use cases:&lt;/strong&gt; Product descriptions, user profiles, document editing, and administrative settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. When Should You Choose Pessimistic Locking?
&lt;/h3&gt;

&lt;p&gt;Choose pessimistic locking when concurrent operations are likely and conflicting updates must be serialized.&lt;/p&gt;

&lt;p&gt;Instead of allowing multiple transactions to modify the same record and detecting conflicts afterward, pessimistic locking acquires a database lock before performing the critical operation.&lt;/p&gt;

&lt;p&gt;Rails provides pessimistic locking through methods such as with_lock and lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-world example: Booking the last available seat
&lt;/h3&gt;

&lt;p&gt;Imagine a flight has only one seat remaining. Two customers try to book it at nearly the same time.&lt;/p&gt;

&lt;p&gt;The application must ensure that only one customer can successfully reserve that seat.&lt;br&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;FlightSeat&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;seat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FlightSeat&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;seat_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;"Seat already booked"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;booked?&lt;/span&gt;
  &lt;span class="n"&gt;seat&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;booked: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lock method issues a locking read, typically using SELECT ... FOR UPDATE on PostgreSQL. If another transaction already holds a conflicting row lock, the second transaction waits until that lock is released.&lt;/p&gt;

&lt;p&gt;Once the first transaction commits, the second transaction can proceed and check the latest committed state. It then sees that the seat is booked and cannot reserve it again.&lt;/p&gt;

&lt;p&gt;The lock is released when the transaction ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose pessimistic locking when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Concurrent updates to the same record are likely.&lt;/li&gt;
&lt;li&gt;An operation involves limited resources, such as seats, inventory, or account balances.&lt;/li&gt;
&lt;li&gt;Conflicting operations must be serialized to maintain consistency.&lt;/li&gt;
&lt;li&gt;Retrying failed operations would be expensive or difficult.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical use cases:&lt;/strong&gt; Inventory deductions, seat reservations, financial balance updates, and processing shared resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Can You Use Both Locking Strategies in One Application?
&lt;/h3&gt;

&lt;p&gt;Yes. Different operations can require different strategies.&lt;/p&gt;

&lt;p&gt;For example, in an e-commerce application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editing product descriptions:&lt;/strong&gt; Optimistic locking, because concurrent edits are relatively uncommon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reducing stock during checkout:&lt;/strong&gt; Pessimistic locking when you need to serialize updates to a specific inventory row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updating account settings:&lt;/strong&gt; Optimistic locking, especially when users edit forms over a longer period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocating a limited resource:&lt;/strong&gt; Pessimistic locking when competing transactions must check and claim the same resource safely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose the strategy based on the operation’s concurrency characteristics and business requirements, rather than applying one locking strategy to every model.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. What Should You Consider Before Choosing?
&lt;/h3&gt;

&lt;p&gt;Before implementing either strategy, ask yourself these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;How frequently can conflicts occur?&lt;/strong&gt; If simultaneous updates are rare, optimistic locking may be sufficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens if an update fails?&lt;/strong&gt; If a user can safely reload and retry, optimistic locking is often appropriate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Must the operation be serialized?&lt;/strong&gt; If competing transactions must check and claim a resource in sequence, pessimistic locking may be a better fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How long will the transaction run?&lt;/strong&gt; Keep pessimistic locking transactions short. Never hold a database lock while waiting for user input or making slow external HTTP calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What other safeguards are needed?&lt;/strong&gt; Database constraints, atomic updates, and appropriate isolation levels can be essential regardless of the locking strategy.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;The choice between optimistic and pessimistic locking depends on how your application handles concurrency.&lt;/p&gt;

&lt;p&gt;Use &lt;strong&gt;optimistic locking&lt;/strong&gt; when conflicts are uncommon and you can detect and resolve stale updates. Use &lt;strong&gt;pessimistic locking&lt;/strong&gt; when concurrent operations must be serialized to protect shared resources.&lt;/p&gt;

&lt;p&gt;The goal isn’t to lock everything. It’s to select the simplest concurrency-control strategy that preserves your application’s data integrity without unnecessarily reducing performance.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>rails</category>
      <category>ruby</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 15 Oct 2026 08:59:36 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/-172f</link>
      <guid>https://dev.to/yashika_vijayvargiya/-172f</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4" class="crayons-story__hidden-navigation-link"&gt;Your Rails Transaction Rolled Back. What Happened to the API Call, Email, or Job?&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/yashika_vijayvargiya" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3977303%2F1ec6531e-a0cf-4905-b074-574c5cf458f5.JPG" alt="yashika_vijayvargiya profile" class="crayons-avatar__image" width="800" height="600"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/yashika_vijayvargiya" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Yashika Vijayvargiya
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Yashika Vijayvargiya
                
                
              
              &lt;div id="story-author-preview-content-4727646" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/yashika_vijayvargiya" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3977303%2F1ec6531e-a0cf-4905-b074-574c5cf458f5.JPG" class="crayons-avatar__image" alt="" width="800" height="600"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Yashika Vijayvargiya&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4" id="article-link-4727646"&gt;
          Your Rails Transaction Rolled Back. What Happened to the API Call, Email, or Job?
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rails"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rails&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/yashika_vijayvargiya/your-rails-transaction-rolled-back-what-happened-to-the-api-call-email-or-job-33m4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>backend</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Git Merge vs Rebase: What Actually Happens to Your Commit History?</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Tue, 29 Sep 2026 07:03:15 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/git-merge-vs-rebase-what-actually-happens-to-your-commit-history-k06</link>
      <guid>https://dev.to/yashika_vijayvargiya/git-merge-vs-rebase-what-actually-happens-to-your-commit-history-k06</guid>
      <description>&lt;p&gt;If you’ve worked with Git for a while, you’ve probably seen commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git merge main
&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;git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both commands can be used to bring changes from one branch into another.&lt;/p&gt;

&lt;p&gt;So a common question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;If both merge and rebase bring my branch up to date, what’s actually the difference?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;strong&gt;Merge combines histories. Rebase rewrites your branch’s history by replaying your commits on top of another branch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But that sentence alone isn’t enough to really understand the difference.&lt;/p&gt;

&lt;p&gt;Let’s start from the beginning.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, understand what a Git branch really is
&lt;/h3&gt;

&lt;p&gt;Before comparing merge and rebase, let’s understand one important thing.&lt;/p&gt;

&lt;p&gt;A Git branch is basically a &lt;strong&gt;movable pointer to a commit&lt;/strong&gt;.&lt;/p&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 dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
        &lt;span class="err"&gt;↑&lt;/span&gt;
       &lt;span class="nv"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;main currently points to commit C.&lt;/p&gt;

&lt;p&gt;Now we create a feature branch:&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 → C
        ↑
       main
        ↑
      feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both branches initially point to the same commit.&lt;/p&gt;

&lt;p&gt;Now you make a change on the feature branch:&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 → C → D
        ↑    ↑
       main feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;feature has moved forward, while main has stayed at C.&lt;/p&gt;

&lt;p&gt;This is the basic idea behind Git branching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Now let’s create a real situation
&lt;/h3&gt;

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

&lt;p&gt;You create a branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that moment:&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 → C
        ↑
       main
        ↑
   feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You make two commits:&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 → C → D → E
        ↑   ↑
       main feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D = Add login form
E = Add authentication logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, another developer adds a change to main:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
        &lt;span class="err"&gt;↑&lt;/span&gt; &lt;span class="err"&gt;↑&lt;/span&gt;
       &lt;span class="nv"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your repository looks 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;               D → E
             ↗ ↑
A → B → C
             ↘
              F
              ↑
             main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More clearly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;              &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;login&lt;/span&gt;
             &lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
             &lt;span class="err"&gt;\&lt;/span&gt;
              &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called &lt;strong&gt;diverged history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your feature branch and main have moved in different directions.&lt;/p&gt;

&lt;p&gt;Now you need to bring main's changes into your feature branch.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;merge and rebase&lt;/strong&gt; come in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Git Merge
&lt;/h3&gt;

&lt;p&gt;You are on your feature branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch feature/login
&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;git merge main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git takes the histories from both branches and creates a new merge commit when a true merge is required.&lt;/p&gt;

&lt;p&gt;The history becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;              &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
             &lt;span class="err"&gt;/&lt;/span&gt;      &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;           &lt;span class="nv"&gt;M&lt;/span&gt;
             &lt;span class="err"&gt;\&lt;/span&gt;     &lt;span class="err"&gt;/&lt;/span&gt;
                &lt;span class="nv"&gt;F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;M is the merge commit.&lt;/p&gt;

&lt;p&gt;The important thing is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git keeps both histories as they actually happened.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your feature work happened on one branch.&lt;/p&gt;

&lt;p&gt;The main work happened on another branch.&lt;/p&gt;

&lt;p&gt;The merge commit records that those two lines were brought together.&lt;/p&gt;

&lt;p&gt;Git’s documentation describes merge as joining development histories and, for a true merge, creating a commit with both branch tips as parents.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does the merge commit mean?
&lt;/h3&gt;

&lt;p&gt;This is important for beginners.&lt;/p&gt;

&lt;p&gt;Suppose you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
        &lt;span class="err"&gt;\&lt;/span&gt;
         &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
              &lt;span class="err"&gt;\&lt;/span&gt;
               &lt;span class="nv"&gt;M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“What code is inside M?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The merge commit represents the &lt;strong&gt;combined result of both branches&lt;/strong&gt;.&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 diff"&gt;&lt;code&gt;&lt;span class="p"&gt;main changes
&lt;/span&gt;     +
&lt;span class="p"&gt;feature changes
&lt;/span&gt;     ↓
&lt;span class="p"&gt;combined result
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The merge commit has two parents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;        &lt;span class="nv"&gt;M&lt;/span&gt;
       &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;E&lt;/span&gt;   &lt;span class="nv"&gt;F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is one of the important differences between merge and rebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fast-forward merge
&lt;/h3&gt;

&lt;p&gt;There is another situation beginners should know about.&lt;/p&gt;

&lt;p&gt;Suppose main hasn't changed since you created your feature branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
        &lt;span class="err"&gt;↑&lt;/span&gt;
       &lt;span class="nv"&gt;main&lt;/span&gt;
        &lt;span class="err"&gt;\&lt;/span&gt;
         &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
              &lt;span class="err"&gt;↑&lt;/span&gt;
           &lt;span class="nv"&gt;feature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch main
git merge feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git doesn’t need to create a merge commit.&lt;/p&gt;

&lt;p&gt;It can simply move main forward:&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 → C → D → E
                ↑
             main
             feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called a &lt;strong&gt;fast-forward merge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There is no divergent history to combine.&lt;/p&gt;

&lt;p&gt;Git simply moves the branch pointer forward. Git’s merge documentation distinguishes this from a true merge commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Git Rebase
&lt;/h3&gt;

&lt;p&gt;Now let’s go back to our original situation.&lt;/p&gt;

&lt;p&gt;We have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;              &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
             &lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
             &lt;span class="err"&gt;\&lt;/span&gt;
              &lt;span class="nv"&gt;F&lt;/span&gt;
              &lt;span class="err"&gt;↑&lt;/span&gt;
             &lt;span class="nv"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are on:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Instead of merging main, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds complicated, but the basic idea is surprisingly simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rebase takes your feature commits and replays them on top of the latest **&lt;/strong&gt; main.**&lt;/p&gt;

&lt;p&gt;Git’s official documentation describes rebase as taking the changes introduced by commits on one branch and replaying them on another base.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;              &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
             &lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
             &lt;span class="err"&gt;\&lt;/span&gt;
              &lt;span class="nv"&gt;F&lt;/span&gt;
&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 shell"&gt;&lt;code&gt;git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git effectively does this:&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 → C → F → D' → E'
&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;A → B → C → F → D' → E'
                  ↑ ↑
              new D new E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your feature commits have been replayed on top of the latest main.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait… why are D and E now D’ and E’?
&lt;/h3&gt;

&lt;p&gt;This is one of the most important concepts in understanding rebase.&lt;/p&gt;

&lt;p&gt;You originally had:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After rebase, Git creates &lt;strong&gt;new commits&lt;/strong&gt; based on the same changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D'
E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They may contain the same code changes, but they are &lt;strong&gt;different Git commits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because a Git commit is not identified only by its code changes.&lt;/p&gt;

&lt;p&gt;Its identity also depends on its parent and other metadata.&lt;/p&gt;

&lt;p&gt;Before rebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After rebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C → F → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since D' has a different parent than D, its commit identity changes.&lt;/p&gt;

&lt;p&gt;This is why rebase &lt;strong&gt;rewrites history&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The easiest way to remember it
&lt;/h3&gt;

&lt;p&gt;Think of &lt;strong&gt;merge&lt;/strong&gt;  as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Let’s combine these two roads.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And &lt;strong&gt;rebase&lt;/strong&gt;  as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Let’s move my road so it starts from the latest point on the other road.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Merge
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;        &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
      &lt;span class="err"&gt;/&lt;/span&gt;      &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;     &lt;span class="nv"&gt;M&lt;/span&gt;
     &lt;span class="err"&gt;\&lt;/span&gt;       &lt;span class="err"&gt;/&lt;/span&gt;
       &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rebase
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;        &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
      &lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
&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 plaintext"&gt;&lt;code&gt;A → B → C → F → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A real-world analogy
&lt;/h3&gt;

&lt;p&gt;Imagine you’re writing a book.&lt;/p&gt;

&lt;p&gt;You started writing Chapter 5 based on version 1 of the book.&lt;/p&gt;

&lt;p&gt;Meanwhile, another writer updated Chapters 1–4.&lt;/p&gt;

&lt;p&gt;You now have two choices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge
&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Let’s keep both versions of history and combine the work.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The final history shows that two people worked in parallel and their work was combined later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rebase
&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Let’s take my Chapter 5 changes and apply them to the newest version of Chapters 1–4.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the history looks as though your Chapter 5 work happened after the latest version.&lt;/p&gt;

&lt;p&gt;That’s essentially what rebase does to commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge vs Rebase at a glance
&lt;/h3&gt;

&lt;p&gt;MergeRebaseCombines branchesYesYesCreates merge commitUsually, when histories divergeNo, not for the rebased commitsKeeps original branch historyYesNoRewrites commitsNoYesProduces linear historyNot necessarilyUsuallySafer for shared/published historyGenerallyRequires cautionUseful for cleaning local historyLess soYes&lt;/p&gt;

&lt;p&gt;The biggest difference is  &lt;strong&gt;history&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does rebase change the final code?
&lt;/h3&gt;

&lt;p&gt;This is another common beginner question.&lt;/p&gt;

&lt;p&gt;Suppose both operations are performed correctly and conflicts are resolved consistently.&lt;/p&gt;

&lt;p&gt;The final project files can be the same.&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;Merge result:

A → B → C → F → M


Rebase result:

A → B → C → F → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commit history is different, but the final snapshot can represent the same combined code.&lt;/p&gt;

&lt;p&gt;Git’s documentation explicitly notes that the final snapshot can be the same while the history differs.&lt;/p&gt;

&lt;p&gt;So the major difference isn’t:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Which one gives me the correct code?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;“How do I want the project’s history to look?”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why do developers like rebase?
&lt;/h3&gt;

&lt;p&gt;One major reason is &lt;strong&gt;cleaner history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine your project has:&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 A
 ├── feature B
 ├── feature C
 └── feature D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everyone frequently merges main into their feature branches, history can become filled with commits like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Merge branch 'main' into feature/login
Merge branch 'main' into feature/login
Merge branch 'main' into feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The history becomes harder to read.&lt;/p&gt;

&lt;p&gt;With rebase, a developer can update their local feature branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and keep their own commits on top of the latest main.&lt;/p&gt;

&lt;p&gt;The result can look much more linear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;G&lt;/span&gt;
&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 dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;───────────────&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt;
         &lt;span class="err"&gt;\&lt;/span&gt;              &lt;span class="err"&gt;/&lt;/span&gt;
          &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git’s documentation specifically notes that rebasing can produce a cleaner, linear history.&lt;/p&gt;

&lt;h3&gt;
  
  
  But if rebase is cleaner, why not always use it?
&lt;/h3&gt;

&lt;p&gt;Because &lt;strong&gt;rebase rewrites history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is where beginners can get into trouble.&lt;/p&gt;

&lt;p&gt;Imagine you’ve already pushed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to GitHub.&lt;/p&gt;

&lt;p&gt;Another developer has based their work on commit D.&lt;/p&gt;

&lt;p&gt;Now you rebase your branch.&lt;/p&gt;

&lt;p&gt;Git may create:&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 → C → D'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;D' is a new commit.&lt;/p&gt;

&lt;p&gt;From Git’s perspective:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D ≠ D'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have rewritten the branch’s history.&lt;/p&gt;

&lt;p&gt;If you force-push that rewritten history, the other developer may have to deal with the changed commit ancestry.&lt;/p&gt;

&lt;p&gt;That’s why rebasing &lt;strong&gt;shared/published branches requires caution&lt;/strong&gt;. Git’s documentation recommends avoiding rebasing commits that have already been published unless you understand the consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  A simple rule for beginners
&lt;/h3&gt;

&lt;p&gt;Remember this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Rebase your private/local work. Be careful rebasing shared work.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&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;feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is your personal branch.&lt;/p&gt;

&lt;p&gt;You can generally rebase it while you’re developing.&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 plaintext"&gt;&lt;code&gt;main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is shared by the whole team.&lt;/p&gt;

&lt;p&gt;Rewriting main history is usually a very different situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when there is a conflict?
&lt;/h3&gt;

&lt;p&gt;Both merge and rebase can have conflicts.&lt;/p&gt;

&lt;p&gt;Suppose you changed:&lt;br&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;welcome_message&lt;/span&gt;
  &lt;span class="s2"&gt;"Welcome!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and another branch changed the same lines:&lt;br&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;welcome_message&lt;/span&gt;
  &lt;span class="s2"&gt;"Welcome back!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git may not know which version you want.&lt;/p&gt;

&lt;p&gt;You’ll see a conflict.&lt;/p&gt;

&lt;p&gt;With merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git merge main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you resolve the conflict and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git merge &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on the merge state/workflow.&lt;/p&gt;

&lt;p&gt;With rebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you resolve the conflict and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you decide that you don’t want to continue the rebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;--abort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git provides --abort specifically to stop a rebase and attempt to restore the state from before the rebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why can rebase feel harder during conflicts?
&lt;/h3&gt;

&lt;p&gt;Because rebase processes your commits &lt;strong&gt;one at a time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine your feature has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During rebase, Git essentially replays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D'
E'
F'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If D conflicts, Git stops.&lt;/p&gt;

&lt;p&gt;You resolve it.&lt;/p&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;git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git moves to the next commit.&lt;/p&gt;

&lt;p&gt;If E also conflicts, you resolve that one too.&lt;/p&gt;

&lt;p&gt;So you may encounter multiple conflict resolutions during one rebase.&lt;/p&gt;

&lt;p&gt;This happens because rebase replays the commits individually.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens during a merge conflict?
&lt;/h3&gt;

&lt;p&gt;Merge generally considers the two branch tips and their common ancestor to combine the changes.&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 dot"&gt;&lt;code&gt;              &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
            &lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt;
            &lt;span class="err"&gt;\&lt;/span&gt;
             &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git compares the relevant histories and attempts to produce one combined result.&lt;/p&gt;

&lt;p&gt;If Git can’t automatically determine the correct result, it stops and asks you to resolve the conflict.&lt;/p&gt;

&lt;p&gt;Git’s normal merge strategy is a three-way merge using the two branch tips and their merge base.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rebase is not “better merge”
&lt;/h3&gt;

&lt;p&gt;This is an important point.&lt;/p&gt;

&lt;p&gt;You will sometimes hear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Always rebase. Merge is bad.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s an oversimplification.&lt;/p&gt;

&lt;p&gt;Merge and rebase solve related problems but have different effects on history.&lt;/p&gt;

&lt;p&gt;Git’s own documentation doesn’t say that one is universally better. The appropriate choice depends on the project and team workflow.&lt;/p&gt;

&lt;p&gt;A team may deliberately prefer merge commits because they want to preserve the history of how branches were integrated.&lt;/p&gt;

&lt;p&gt;Another team may prefer a linear history because it makes the project easier to navigate.&lt;/p&gt;

&lt;p&gt;Neither approach is universally correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: working on a feature in a team
&lt;/h3&gt;

&lt;p&gt;Suppose you’re working on:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Meanwhile, main has received new changes.&lt;/p&gt;

&lt;p&gt;You want the latest main changes before opening/updating your PR.&lt;/p&gt;

&lt;p&gt;You can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your history becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt;

&lt;span class="nv"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
&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 shell"&gt;&lt;code&gt;git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you get:&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 → C → D → E' → F'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your feature commits now sit on top of the latest main.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if you use merge instead?
&lt;/h3&gt;

&lt;p&gt;You could instead do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
git merge origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The history could become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;          &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;F&lt;/span&gt;
         &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch contains the same combined work, but the history records the merge explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does git pull --rebase mean?
&lt;/h3&gt;

&lt;p&gt;You may also have seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another source of confusion.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;essentially does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch
+
integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on your configuration/options, that integration can use merge or rebase. Git documents git pull --rebase as fetching and then rebasing the local work onto the fetched branch.&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 shell"&gt;&lt;code&gt;git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;roughly means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get latest remote changes
        ↓
Put my local commits on top of them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&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 plaintext"&gt;&lt;code&gt;remote: A → B → C
local:           \
                 D → E
&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 plaintext"&gt;&lt;code&gt;A → B → C → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  git pull vs git pull --rebase
&lt;/h3&gt;

&lt;p&gt;This is a useful distinction:&lt;/p&gt;

&lt;h3&gt;
  
  
  Regular pull
&lt;/h3&gt;

&lt;p&gt;Depending on configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can integrate remote changes using merge.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;─────&lt;/span&gt; &lt;span class="nv"&gt;M&lt;/span&gt;
         &lt;span class="err"&gt;\&lt;/span&gt;     &lt;span class="err"&gt;/&lt;/span&gt;
          &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pull with rebase
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull &lt;span class="nt"&gt;--rebase&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gives you:&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 → C → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the goal is usually to avoid unnecessary merge commits in your local branch history.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should a fresher use?
&lt;/h3&gt;

&lt;p&gt;For someone new to Git, I recommend first becoming comfortable with  &lt;strong&gt;merge&lt;/strong&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch
git fetch
git merge
git pull
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you understand branches and commits, learn rebase.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because merge is conceptually closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“These two branches have changes. Combine them.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rebase requires understanding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Take these existing commits and recreate them on a different base.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once that mental model clicks, rebase becomes much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  A simple decision guide
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Use merge when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want to preserve the actual branching history.&lt;/li&gt;
&lt;li&gt;The branch is shared by multiple developers.&lt;/li&gt;
&lt;li&gt;You don’t want to rewrite existing history.&lt;/li&gt;
&lt;li&gt;Your team prefers merge commits.&lt;/li&gt;
&lt;li&gt;You are integrating a completed branch into another branch.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch main
git merge feature/login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use rebase when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You’re updating your own feature branch.&lt;/li&gt;
&lt;li&gt;You want a cleaner linear history.&lt;/li&gt;
&lt;li&gt;Your commits haven’t been shared yet.&lt;/li&gt;
&lt;li&gt;Your team follows a rebase-based workflow.&lt;/li&gt;
&lt;li&gt;You want to replay your work on top of the latest main.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch feature/login
git fetch origin
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One important warning about force push
&lt;/h3&gt;

&lt;p&gt;After rebasing a branch that has already been pushed, you may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--force-with-lease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--force-with-lease
&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;--force
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;--force-with-lease provides an additional safety check so you don't blindly overwrite a remote branch that has moved unexpectedly.&lt;/p&gt;

&lt;p&gt;But even with --force-with-lease, you should understand that you're rewriting the remote branch's history.&lt;/p&gt;

&lt;p&gt;Don’t casually rebase and force-push a branch that other developers are actively using.&lt;/p&gt;

&lt;h3&gt;
  
  
  The easiest mental model
&lt;/h3&gt;

&lt;p&gt;If you remember only one thing from this article, remember this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Keep both histories and connect them.&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;      &lt;span class="nv"&gt;D&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;E&lt;/span&gt;
     &lt;span class="err"&gt;/&lt;/span&gt;     &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;       &lt;span class="nv"&gt;M&lt;/span&gt;
     &lt;span class="err"&gt;\&lt;/span&gt;     &lt;span class="err"&gt;/&lt;/span&gt;
      &lt;span class="nv"&gt;F&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rebase
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Take my commits and replay them on top of the latest base.&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

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

      D → E
     /
A → B → C → F

After:

A → B → C → F → D' → E'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final comparison
&lt;/h3&gt;

&lt;p&gt;Imagine you and another developer start from the same point:&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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C → D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E → F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With merge
&lt;/h3&gt;

&lt;p&gt;Git says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“You both worked from B. I’ll combine the two histories.”&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;        &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;D&lt;/span&gt;
      &lt;span class="err"&gt;/&lt;/span&gt;      &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;A&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;B&lt;/span&gt;         &lt;span class="nv"&gt;M&lt;/span&gt;
      &lt;span class="err"&gt;\&lt;/span&gt;       &lt;span class="err"&gt;/&lt;/span&gt;
       &lt;span class="nv"&gt;E&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt;  &lt;span class="nv"&gt;F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With rebase
&lt;/h3&gt;

&lt;p&gt;Git says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I’ll take your C and D changes and replay them after E and F.”&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B → E → F → C' → D'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s the core difference.&lt;/p&gt;

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

&lt;p&gt;git merge and git rebase aren't simply two commands that do the same thing.&lt;/p&gt;

&lt;p&gt;They represent two different approaches to  &lt;strong&gt;history&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge preserves the branching story.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rebase rewrites your branch so your commits appear on top of another branch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re working on your own feature branch, rebasing onto the latest main can give you a clean, linear history.&lt;/p&gt;

&lt;p&gt;If you’re working with shared history where preserving existing commits and branch relationships matters, merging can be the safer choice.&lt;/p&gt;

&lt;p&gt;The most important thing isn’t memorizing:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;It’s understanding what Git is doing to your commit graph.&lt;/p&gt;

&lt;p&gt;Once you understand the graph, the commands become much easier to remember.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick cheat sheet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Merge&lt;/span&gt;
git switch feature/login
git merge main

&lt;span class="c"&gt;# Rebase&lt;/span&gt;
git switch feature/login
git rebase main

&lt;span class="c"&gt;# Update remote information first&lt;/span&gt;
git fetch origin

&lt;span class="c"&gt;# Rebase your feature onto remote main&lt;/span&gt;
git rebase origin/main

&lt;span class="c"&gt;# Continue after resolving a rebase conflict&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;

&lt;span class="c"&gt;# Cancel an ongoing rebase&lt;/span&gt;
git rebase &lt;span class="nt"&gt;--abort&lt;/span&gt;

&lt;span class="c"&gt;# Push rewritten history safely&lt;/span&gt;
git push &lt;span class="nt"&gt;--force-with-lease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Remember:&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;MERGE → combine histories

REBASE → replay commits on a new base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>github</category>
      <category>git</category>
      <category>difference</category>
      <category>programming</category>
    </item>
    <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>
  </channel>
</rss>
