<?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>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;A bug doesn't always need hundreds of lines of code to cause a real production 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;blockquote&gt;
&lt;p&gt;"Every tag is a String."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That assumption lived inside a Ruby filtering method in the open-source &lt;a href="https://github.com/rubyevents/rubyevents" rel="noopener noreferrer"&gt;RubyEvents&lt;/a&gt; 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 error.&lt;/p&gt;

&lt;p&gt;This is the story of how I tracked it down and fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;The issue was reported as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Cannot Click tags on Newsletter"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The behavior was straightforward:&lt;/p&gt;

&lt;ol&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;/ol&gt;

&lt;p&gt;The issue was tracked as &lt;a href="https://github.com/rubyevents/rubyevents/issues/1836" rel="noopener noreferrer"&gt;#1836&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, this sounds like 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 tag-filtering logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Assumption
&lt;/h2&gt;

&lt;p&gt;The filtering logic lived in &lt;code&gt;Announcement::Collection#by_tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The original implementation looked 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;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;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="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;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="c1"&gt;# =&amp;gt; ["ruby", "rails"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;map(&amp;amp;:downcase)&lt;/code&gt; contains an important assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every element must respond to &lt;code&gt;downcase&lt;/code&gt;.&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;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;code&gt;NoMethodError&lt;/code&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 tag-filtering code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interesting Part: Fixing the Bug Without Changing the Behavior
&lt;/h2&gt;

&lt;p&gt;There were several ways this could have been "fixed."&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 change the scope of the fix.&lt;/p&gt;

&lt;p&gt;The filtering method already had a clear responsibility:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find announcements whose tags match the requested tag, case-insensitively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The safer approach was to make that comparison resilient to the actual values it receives.&lt;/p&gt;

&lt;p&gt;I changed the implementation to:&lt;br&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;h3&gt;
  
  
  1. &lt;code&gt;to_s&lt;/code&gt; creates a safe comparison boundary
&lt;/h3&gt;

&lt;p&gt;Instead of assuming:&lt;br&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 ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"Ruby"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;span class="c1"&gt;# =&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 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;to_s&lt;/span&gt;
&lt;span class="c1"&gt;# =&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;h3&gt;
  
  
  2. &lt;code&gt;casecmp?&lt;/code&gt; expresses the actual requirement
&lt;/h3&gt;

&lt;p&gt;The original code 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 "convert everything to lowercase."&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Compare these values without considering case.&lt;/p&gt;
&lt;/blockquote&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;h2&gt;
  
  
  Why &lt;code&gt;any?&lt;/code&gt; Instead of &lt;code&gt;map&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;This is 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;The new implementation asks the question 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="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;We don't actually need a transformed array.&lt;/p&gt;

&lt;p&gt;We only need to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does at least one tag match?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;any?&lt;/code&gt; expresses that directly.&lt;/p&gt;

&lt;p&gt;It also means we can stop looking as soon as a matching tag is found.&lt;/p&gt;

&lt;p&gt;So the new implementation is not simply more defensive.&lt;/p&gt;

&lt;p&gt;It is also closer to the intent of the operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before vs After
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before
&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;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one doesn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NoMethodError
   ↓
request fails
   ↓
user cannot follow the newsletter tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After
&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;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 the comparison becomes:&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;h2&gt;
  
  
  Why This Bug Is Easy to Miss
&lt;/h2&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, it is perfectly reasonable Ruby 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="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 production 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 "never use &lt;code&gt;downcase&lt;/code&gt;."&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Know where your assumptions about data types are coming from.&lt;/strong&gt;&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;h2&gt;
  
  
  The Final Change
&lt;/h2&gt;

&lt;p&gt;The actual production 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;/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 is 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 six checks, and was merged into the project's &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/rubyevents/rubyevents/pull/1847" rel="noopener noreferrer"&gt;#1847 — Fix tag filtering for non-string tags&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Issue: &lt;a href="https://github.com/rubyevents/rubyevents/issues/1836" rel="noopener noreferrer"&gt;#1836 — Cannot Click tags on Newsletter&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Small bugs can expose bigger assumptions
&lt;/h3&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;blockquote&gt;
&lt;p&gt;Do I know for certain that every item responds to this method?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question becomes particularly important at boundaries where data may come from different sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Fix the behavior, not just the exception
&lt;/h3&gt;

&lt;p&gt;It would have been easy to focus only on preventing the &lt;code&gt;NoMethodError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the goal should be broader:&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;h3&gt;
  
  
  3. Ruby's expressive methods can make intent clearer
&lt;/h3&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 &lt;strong&gt;any tag matches&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That distinction matters when maintaining code long after the original bug has been forgotten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source Contributions Are Full of These Bugs
&lt;/h2&gt;

&lt;p&gt;One of the things 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 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 found one imperfect assumption.&lt;/p&gt;

&lt;p&gt;You have to understand the existing behavior, make the smallest responsible change, and prove that the fix doesn't break what was already working.&lt;/p&gt;

&lt;p&gt;That's exactly what made this bug 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;h3&gt;
  
  
  The Takeaway
&lt;/h3&gt;

&lt;p&gt;A production 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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="s2"&gt;"Every tag is a String."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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;&lt;strong&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;/strong&gt;&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>ruby</category>
      <category>opensource</category>
      <category>debugging</category>
    </item>
    <item>
      <title>What Is Ponytail? A Beginner’s Guide to the AI Coding Companion Everyone Is Talking About</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 05 Aug 2026 09:29:26 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/what-is-ponytail-a-beginners-guide-to-the-ai-coding-companion-everyone-is-talking-about-1d6g</link>
      <guid>https://dev.to/yashika_vijayvargiya/what-is-ponytail-a-beginners-guide-to-the-ai-coding-companion-everyone-is-talking-about-1d6g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;⚠️ Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;AI developer tools evolve incredibly fast. Installation steps, supported editors, AI models, and configuration options may change over time. This article is&lt;/em&gt; &lt;strong&gt;&lt;em&gt;not intended to be official documentation&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;. Instead, its goal is to help you understand&lt;/em&gt; &lt;strong&gt;&lt;em&gt;what Ponytail is, why it exists, and when you might want to use it&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;. Before installing or configuring Ponytail, always refer to its official documentation for the latest instructions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;If you’ve recently spent time on X (Twitter), Reddit, or developer communities, you’ve probably seen people talking about &lt;strong&gt;Ponytail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some developers call it an AI coding assistant.&lt;/p&gt;

&lt;p&gt;Others think it’s an alternative to Cursor.&lt;/p&gt;

&lt;p&gt;Some believe it’s a replacement for Claude Code.&lt;/p&gt;

&lt;p&gt;And many assume it’s another Large Language Model (LLM) like ChatGPT or Claude.&lt;/p&gt;

&lt;p&gt;None of these descriptions are entirely accurate.&lt;/p&gt;

&lt;p&gt;This confusion exists because Ponytail is different from most AI tools developers are familiar with.&lt;/p&gt;

&lt;p&gt;In this article, we’ll break down exactly what Ponytail is, what it is not, how it fits into the AI coding ecosystem, and when it makes sense to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Biggest Misconception About Ponytail
&lt;/h3&gt;

&lt;p&gt;The first thing to understand is this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ponytail is not an AI model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It cannot answer questions by itself.&lt;/p&gt;

&lt;p&gt;It cannot generate code by itself.&lt;/p&gt;

&lt;p&gt;It cannot replace ChatGPT, Claude, Gemini, or any other language model.&lt;/p&gt;

&lt;p&gt;Instead, Ponytail works &lt;strong&gt;alongside&lt;/strong&gt; an AI coding agent.&lt;/p&gt;

&lt;p&gt;Think of it as a layer that influences how the AI approaches programming tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the AI Coding Ecosystem
&lt;/h3&gt;

&lt;p&gt;Many developers mix together editors, AI models, and coding agents.&lt;/p&gt;

&lt;p&gt;These are different pieces of the puzzle.&lt;/p&gt;

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

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code
    ↓
AI Coding Agent
    ↓
LLM (Claude / GPT / Gemini)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a different responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code
&lt;/h3&gt;

&lt;p&gt;VS Code is simply your editor.&lt;/p&gt;

&lt;p&gt;It doesn’t understand your code or generate anything on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Model
&lt;/h3&gt;

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

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

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

&lt;p&gt;These are language models.&lt;/p&gt;

&lt;p&gt;They understand natural language and generate code.&lt;/p&gt;

&lt;p&gt;However, by themselves they don’t know how to edit files inside your project or interact with your development environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Coding Agent
&lt;/h3&gt;

&lt;p&gt;An AI coding agent connects your editor to an AI model.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Read project files&lt;/li&gt;
&lt;li&gt;Edit code&lt;/li&gt;
&lt;li&gt;Create new files&lt;/li&gt;
&lt;li&gt;Run commands&lt;/li&gt;
&lt;li&gt;Analyze your repository&lt;/li&gt;
&lt;li&gt;Suggest refactoring&lt;/li&gt;
&lt;li&gt;Generate tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples include various AI-powered coding assistants and editor integrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ponytail
&lt;/h3&gt;

&lt;p&gt;Ponytail sits on top of this workflow.&lt;/p&gt;

&lt;p&gt;Instead of replacing your AI, it attempts to improve &lt;strong&gt;how the AI behaves while writing code&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Analogy
&lt;/h3&gt;

&lt;p&gt;Imagine you’re hiring a software engineer.&lt;/p&gt;

&lt;p&gt;Without guidance, they might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create unnecessary abstractions&lt;/li&gt;
&lt;li&gt;introduce extra classes&lt;/li&gt;
&lt;li&gt;over-engineer a simple solution&lt;/li&gt;
&lt;li&gt;use complex design patterns for small problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine giving that engineer a document that says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep things simple.&lt;/li&gt;
&lt;li&gt;Don’t create classes unless necessary.&lt;/li&gt;
&lt;li&gt;Prefer existing libraries.&lt;/li&gt;
&lt;li&gt;Avoid unnecessary complexity.&lt;/li&gt;
&lt;li&gt;Write code another developer can understand quickly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engineer hasn’t changed.&lt;/p&gt;

&lt;p&gt;Their instructions have.&lt;/p&gt;

&lt;p&gt;Ponytail plays a similar role for AI coding assistants.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Problem Does Ponytail Solve?
&lt;/h3&gt;

&lt;p&gt;As AI became better at writing code, developers noticed recurring issues.&lt;/p&gt;

&lt;p&gt;The AI often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates more files than necessary&lt;/li&gt;
&lt;li&gt;writes excessive boilerplate&lt;/li&gt;
&lt;li&gt;introduces unnecessary abstractions&lt;/li&gt;
&lt;li&gt;overuses design patterns&lt;/li&gt;
&lt;li&gt;invents helper classes&lt;/li&gt;
&lt;li&gt;makes small tasks more complicated than they need to be&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes this is useful.&lt;/p&gt;

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

&lt;p&gt;Ponytail aims to steer the AI toward simpler, more maintainable solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Ponytail Is NOT
&lt;/h3&gt;

&lt;p&gt;Ponytail is  &lt;strong&gt;not&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;another version of ChatGPT&lt;/li&gt;
&lt;li&gt;another LLM&lt;/li&gt;
&lt;li&gt;a replacement for Claude&lt;/li&gt;
&lt;li&gt;a replacement for Cursor&lt;/li&gt;
&lt;li&gt;an IDE&lt;/li&gt;
&lt;li&gt;a code editor&lt;/li&gt;
&lt;li&gt;a compiler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this distinction removes most of the confusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Ponytail Work Without an AI?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;If you only install Ponytail, nothing happens.&lt;/p&gt;

&lt;p&gt;You still need an AI coding agent backed by an AI model.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VS Code
❌

VS Code + Ponytail
❌

VS Code + AI Agent
✅

VS Code + AI Agent + Ponytail
✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Does Ponytail Replace Cursor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Cursor is a complete AI-powered editor.&lt;/p&gt;

&lt;p&gt;Ponytail is not.&lt;/p&gt;

&lt;p&gt;Cursor provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;editor integration&lt;/li&gt;
&lt;li&gt;AI chat&lt;/li&gt;
&lt;li&gt;autocomplete&lt;/li&gt;
&lt;li&gt;repository understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ponytail focuses on influencing how an AI coding assistant approaches problems rather than replacing the editor itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail Replace Claude?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Claude is the intelligence.&lt;/p&gt;

&lt;p&gt;Ponytail provides guidance.&lt;/p&gt;

&lt;p&gt;Without Claude (or another supported model), Ponytail has nothing to reason with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail Reduce Token Usage?
&lt;/h3&gt;

&lt;p&gt;This is one of the most common questions.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Not directly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ponytail does not magically reduce the number of tokens consumed.&lt;/p&gt;

&lt;p&gt;However, by encouraging the AI to produce smaller, simpler solutions with fewer unnecessary edits, some workflows may naturally involve fewer generated tokens.&lt;/p&gt;

&lt;p&gt;This depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the AI model&lt;/li&gt;
&lt;li&gt;the coding task&lt;/li&gt;
&lt;li&gt;the prompts&lt;/li&gt;
&lt;li&gt;the agent’s behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So any reduction is a side effect of simpler outputs, not a guaranteed feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Ponytail Free?
&lt;/h3&gt;

&lt;p&gt;Ponytail itself has generally been made available as an open-source project.&lt;/p&gt;

&lt;p&gt;However, remember:&lt;/p&gt;

&lt;p&gt;Using Ponytail does &lt;strong&gt;not&lt;/strong&gt; eliminate the need for an AI model.&lt;/p&gt;

&lt;p&gt;If your coding assistant relies on a paid AI service or API, those costs still apply.&lt;/p&gt;

&lt;p&gt;Think of Ponytail as improving the workflow rather than replacing the underlying AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Use Ponytail?
&lt;/h3&gt;

&lt;p&gt;Ponytail can be valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;working on large production codebases&lt;/li&gt;
&lt;li&gt;contributing to open-source projects&lt;/li&gt;
&lt;li&gt;maintaining mature Rails applications&lt;/li&gt;
&lt;li&gt;reviewing AI-generated code&lt;/li&gt;
&lt;li&gt;encouraging simpler implementations&lt;/li&gt;
&lt;li&gt;reducing unnecessary abstractions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are situations where readability and maintainability often matter more than generating the maximum amount of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Should You Avoid It?
&lt;/h3&gt;

&lt;p&gt;Ponytail may not add much value if you’re:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;learning programming from scratch&lt;/li&gt;
&lt;li&gt;writing small throwaway scripts&lt;/li&gt;
&lt;li&gt;experimenting with new ideas&lt;/li&gt;
&lt;li&gt;using AI only for quick code snippets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, a standard AI assistant may already be sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Rails Example
&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Optimize this ActiveRecord query.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without guidance, the AI might introduce several service objects, helper modules, and layers of abstraction.&lt;/p&gt;

&lt;p&gt;With a “keep it simple” philosophy, the AI is more likely to suggest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding the correct index&lt;/li&gt;
&lt;li&gt;using includes&lt;/li&gt;
&lt;li&gt;avoiding N+1 queries&lt;/li&gt;
&lt;li&gt;simplifying the ActiveRecord chain&lt;/li&gt;
&lt;li&gt;making a small, focused improvement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This often aligns well with how experienced Rails developers prefer to evolve applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequently Asked Questions
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Is Ponytail another AI?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Ponytail write code?
&lt;/h3&gt;

&lt;p&gt;Not by itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail work without Claude or another AI model?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail replace Cursor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail replace VS Code?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Ponytail a code editor?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Ponytail guarantee better code?
&lt;/h3&gt;

&lt;p&gt;No. It provides guidance, but you should always review AI-generated code before merging it into production.&lt;/p&gt;

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

&lt;p&gt;AI-assisted development is evolving at an incredible pace. New models, editors, and coding agents appear regularly, and the surrounding ecosystem changes just as quickly.&lt;/p&gt;

&lt;p&gt;Ponytail is best thought of as &lt;strong&gt;a way to influence how an AI coding assistant approaches software development&lt;/strong&gt; , encouraging simpler, more maintainable solutions rather than acting as a new AI model or editor.&lt;/p&gt;

&lt;p&gt;Whether Ponytail becomes part of your daily workflow depends on your projects, your preferred tools, and how much you value AI-generated code that favors clarity over complexity.&lt;/p&gt;

&lt;p&gt;As with any AI tool, the most important skill isn’t learning a specific product — it’s understanding where that product fits into the broader development workflow.&lt;/p&gt;

&lt;p&gt;If this article introduced you to Ponytail for the first time, take a few minutes to explore its latest documentation, experiment with it in a small project, and decide whether its philosophy aligns with the way you like to build software.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Part 3: Optimistic Locking in Rails – Preventing Lost Updates Without Blocking Users</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 31 Jul 2026 10:49:53 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/part-3-optimistic-locking-in-rails-preventing-lost-updates-without-blocking-users-hga</link>
      <guid>https://dev.to/yashika_vijayvargiya/part-3-optimistic-locking-in-rails-preventing-lost-updates-without-blocking-users-hga</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the previous article, we learned how Pessimistic Locking prevents concurrent modifications by locking database rows.&lt;/p&gt;

&lt;p&gt;While this guarantees data consistency, it also means other users may have to wait until the lock is released.&lt;/p&gt;

&lt;p&gt;But what if conflicts are rare?&lt;/p&gt;

&lt;p&gt;Should we still lock every row?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Optimistic Locking&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Instead of preventing concurrent updates, Optimistic Locking allows multiple users to edit the same record and detects conflicts only when they try to save their changes.&lt;/p&gt;

&lt;p&gt;It assumes that conflicts are unlikely, making it a great choice for many web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Optimistic Locking?&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Optimistic Locking is a concurrency control strategy that allows multiple transactions to read and modify the same record without acquiring database locks.&lt;/p&gt;

&lt;p&gt;Before saving changes, Rails checks whether the record has been modified by another transaction.&lt;/p&gt;

&lt;p&gt;If it has, Rails raises an exception instead of silently overwriting the newer data.&lt;/p&gt;

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

&lt;p&gt;"I believe no one else will modify this record. But before saving, I'll verify that assumption."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Do We Need Optimistic Locking?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Imagine an admin panel.&lt;/p&gt;

&lt;p&gt;Two administrators open the same product.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16
Price: ₹80,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin A changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price → ₹75,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin B changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title → iPhone 16 Pro

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

&lt;/div&gt;



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

&lt;p&gt;Admin A saves.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16
Price: ₹75,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few seconds later...&lt;/p&gt;

&lt;p&gt;Admin B saves.&lt;/p&gt;

&lt;p&gt;Database becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: iPhone 16 Pro
Price: ₹80,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin A's update is lost.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How Rails Solves This&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rails provides built-in support for Optimistic Locking using a column named:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Whenever a record is updated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rails increments lock_version&lt;/li&gt;
&lt;li&gt;Rails verifies the previous version before updating&lt;/li&gt;
&lt;li&gt;If the version has changed, Rails raises an exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Enabling Optimistic Locking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simply add a column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AddLockVersionToProducts &amp;lt; ActiveRecord::Migration[8.0]
  def change
    add_column :products,
               :lock_version,
               :integer,
               default: 0,
               null: false
  end
end

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

&lt;/div&gt;



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

&lt;p&gt;Rails automatically enables optimistic locking.&lt;/p&gt;

&lt;p&gt;No additional configuration is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Current row:&lt;/p&gt;

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

&lt;p&gt;Admin A loads:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Admin B also loads:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Admin A updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product.price = 75000
product.save!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database:&lt;/p&gt;

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

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

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

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

&lt;/div&gt;



&lt;p&gt;became&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Now Admin B tries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product.title = "iPhone Pro"

product.save!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails generates SQL similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE products SET title = 'iPhone Pro', lock_version = 2 WHERE id = 1 AND lock_version = 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the database contains:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 rows updated

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

&lt;/div&gt;



&lt;p&gt;Rails raises:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Timeline&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

Read Product

Version = 0

↓

Edit

↓

Save

Version becomes 1

----------------------------

Admin B

Read Product

Version = 0

↓

Edit

↓

Try Save

↓

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Happens Internally?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose current version:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Rails updates using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UPDATE products
SET
price = 100,
lock_version = 4
WHERE
id = 1
AND lock_version = 3;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another transaction already updated it:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;The WHERE condition fails.&lt;/p&gt;

&lt;p&gt;No row is updated.&lt;/p&gt;

&lt;p&gt;Rails knows someone modified the record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling the Exception&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typical implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;begin
  product.update!(product_params)

rescue ActiveRecord::StaleObjectError

  flash[:alert] =
    "This product was updated by another user. Please reload and try again."

  redirect_to edit_product_path(product)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of silently overwriting data, the user is informed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Example 1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CMS Article Editing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two editors modify the same article.&lt;/p&gt;

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

&lt;p&gt;One editor overwrites another's work.&lt;/p&gt;

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

&lt;p&gt;Second editor sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"This article has changed since you opened it."

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real Production Example 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Admin Dashboard&lt;/p&gt;

&lt;p&gt;Inventory manager changes:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Manager B changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price = ₹500

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

&lt;/div&gt;



&lt;p&gt;Instead of losing one update,&lt;/p&gt;

&lt;p&gt;Rails detects the conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Example 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User Profile&lt;/p&gt;

&lt;p&gt;Editing profile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Address&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conflicts are rare.&lt;/p&gt;

&lt;p&gt;Blocking users would hurt UX.&lt;/p&gt;

&lt;p&gt;Optimistic locking is a better fit.&lt;/p&gt;

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

&lt;p&gt;✅ No waiting&lt;/p&gt;

&lt;p&gt;✅ Better scalability&lt;/p&gt;

&lt;p&gt;✅ Better user experience&lt;/p&gt;

&lt;p&gt;✅ No database locks&lt;/p&gt;

&lt;p&gt;✅ Great for read-heavy applications&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;❌ Save may fail&lt;/p&gt;

&lt;p&gt;❌ Users must retry&lt;/p&gt;

&lt;p&gt;❌ Not suitable for financial systems&lt;/p&gt;

&lt;p&gt;❌ Doesn't prevent concurrent reads&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimistic vs Pessimistic Locking&lt;/strong&gt;&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Understanding Database Locking in Ruby on Rails: Types, Examples, and Best Practices</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 15 Jul 2026 08:39:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</guid>
      <description>&lt;p&gt;Goal of this article: Build a strong foundation before learning pessimistic locking, optimistic locking, deadlocks, and isolation levels.&lt;/p&gt;

&lt;p&gt;When I started working with production Rails applications, I realized that most database problems were not caused by missing indexes but by concurrent requests modifying the same data. Understanding locking completely changed the way I design payment, inventory, and booking systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a lock?
&lt;/h3&gt;

&lt;p&gt;A lock is a mechanism used by PostgreSQL to protect data from being modified incorrectly by multiple transactions at the same time.&lt;/p&gt;

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

&lt;p&gt;A lock tells other transactions: “Someone is currently working with this data. Wait your turn.”&lt;/p&gt;

&lt;p&gt;Imagine two people editing the same Google Doc at the same moment. Without coordination, one person’s changes could overwrite the other’s. Databases use locks to prevent similar problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do databases need locking?
&lt;/h3&gt;

&lt;p&gt;Suppose an e-commerce application has only 1 item left in inventory.&lt;/p&gt;

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

&lt;p&gt;Without locking, both requests may read:&lt;/p&gt;

&lt;p&gt;Both transactions believe the product is available and both place an order.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

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

&lt;p&gt;This is called a race condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a race condition?
&lt;/h3&gt;

&lt;p&gt;A race condition occurs when the final result depends on the timing of concurrent operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;If two requests execute this code simultaneously, both may read the same inventory value before either update occurs.&lt;/p&gt;

&lt;p&gt;This is the fundamental problem locking is designed to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions: The Foundation of Locking
&lt;/h3&gt;

&lt;p&gt;Locks exist inside database transactions.&lt;/p&gt;

&lt;p&gt;A transaction guarantees that all operations inside the block either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Succeed together (COMMIT)&lt;/li&gt;
&lt;li&gt;Fail together (ROLLBACK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locks are typically acquired when the transaction begins modifying or explicitly locking rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Handles Concurrent Transactions
&lt;/h3&gt;

&lt;p&gt;Consider two transactions.&lt;/p&gt;

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

&lt;p&gt;PostgreSQL automatically prevents both transactions from modifying the same row simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  MVCC: Why SELECT Usually Doesn’t Block
&lt;/h3&gt;

&lt;p&gt;One of PostgreSQL’s most important features is MVCC (Multi-Version Concurrency Control).&lt;/p&gt;

&lt;p&gt;Instead of blocking readers, PostgreSQL creates multiple versions of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Transaction A updates a product but has not committed yet.&lt;/p&gt;

&lt;p&gt;At the same time, Transaction B runs:&lt;/p&gt;

&lt;p&gt;Transaction B usually does not wait. It reads the previous committed version of the row.&lt;/p&gt;

&lt;p&gt;This is why PostgreSQL can handle many readers efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Concurrency Problems
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. Lost Update
&lt;/h3&gt;

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

&lt;p&gt;Problem: Two users overwrite each other’s changes.&lt;/p&gt;

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

&lt;p&gt;Correct result should be: $30&lt;/p&gt;

&lt;p&gt;Actual result: $50&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dirty Read
&lt;/h3&gt;

&lt;p&gt;Usually prevented in PostgreSQL&lt;/p&gt;

&lt;p&gt;Reading data that another transaction has modified but not committed.&lt;/p&gt;

&lt;p&gt;PostgreSQL’s default isolation level (Read Committed) prevents this.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Non-repeatable Read
&lt;/h3&gt;

&lt;p&gt;Possible in Read Committed&lt;/p&gt;

&lt;p&gt;You read the same row twice and get different values because another transaction committed between the reads.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Phantom Read
&lt;/h3&gt;

&lt;p&gt;Isolation-level issue&lt;/p&gt;

&lt;p&gt;A query returns a different set of rows the second time it runs because another transaction inserted matching records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rails Example: Preventing Double Booking
&lt;/h3&gt;

&lt;p&gt;Unsafe code:&lt;/p&gt;

&lt;p&gt;Safe code using locking:&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;generates:&lt;/p&gt;

&lt;p&gt;This acquires a row-level lock so only one transaction can modify that event record at a time.&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  Important Insight: PostgreSQL Uses Multiple Lock Types
&lt;/h3&gt;

&lt;p&gt;Many beginners think there is only one lock.&lt;/p&gt;

&lt;p&gt;In reality PostgreSQL has:&lt;/p&gt;

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

&lt;p&gt;We’ll explore these deeply in later parts of the series.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to See Locks in Production
&lt;/h3&gt;

&lt;p&gt;PostgreSQL exposes lock information through pg_locks.&lt;/p&gt;

&lt;p&gt;A more useful query:&lt;/p&gt;

&lt;p&gt;This helps identify which transaction is holding a lock and which transaction is waiting.&lt;/p&gt;

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

&lt;p&gt;Locks prevent race conditions and data corruption.&lt;/p&gt;

&lt;p&gt;Locks exist inside transactions.&lt;/p&gt;

&lt;p&gt;PostgreSQL uses MVCC so readers usually do not block writers.&lt;/p&gt;

&lt;p&gt;Row locks protect individual records.&lt;/p&gt;

&lt;p&gt;Table locks protect entire tables.&lt;/p&gt;

&lt;p&gt;FOR UPDATE is the foundation of pessimistic locking in Rails.&lt;/p&gt;

&lt;p&gt;Most production concurrency bugs are caused by missing transactions or missing locks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interview Question
&lt;/h3&gt;

&lt;p&gt;Why does PostgreSQL allow many SELECT queries to run without blocking each other?&lt;/p&gt;

&lt;p&gt;Answer: PostgreSQL uses MVCC (Multi-Version Concurrency Control). Instead of blocking readers, it keeps multiple versions of rows so a SELECT query can read the last committed version while another transaction is updating the row. This greatly improves concurrency and is one of PostgreSQL’s biggest advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Article in the Series
&lt;/h3&gt;

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

&lt;p&gt;We will dive into lock, with_lock, FOR UPDATE, NOWAIT, SKIP LOCKED, and real production examples such as inventory reservation and payment processing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/understanding-database-locking-in-rails-postgresql-part-1" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 13, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>howto</category>
      <category>rails</category>
      <category>locking</category>
    </item>
    <item>
      <title>PostgreSQL Index Types Explained with Real Rails Examples</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 08:00:26 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</link>
      <guid>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" width="1024" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part-1 &lt;a href="https://railswithyashika.hashnode.dev/understanding-indexes-before-learning-index-types" rel="noopener noreferrer"&gt;Understanding Indexes before learning Index Types&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Now that we understand what indexes are and why they matter, let’s explore the different types of indexes available in PostgreSQL and when to use them in Rails applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Single Column Index
&lt;/h3&gt;

&lt;p&gt;The simplest and most commonly used index.&lt;/p&gt;
&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  SQL Generated
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;index_users_on_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Email lookups&lt;/li&gt;
&lt;li&gt;SKU lookups&lt;/li&gt;
&lt;li&gt;UUID searches&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sku: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Without an index, PostgreSQL scans the entire table.&lt;/p&gt;

&lt;p&gt;With an index, PostgreSQL can jump directly to the matching row.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Unique Index
&lt;/h3&gt;

&lt;p&gt;A unique index improves lookup performance while also preventing duplicate values.&lt;/p&gt;
&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  What It Solves
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john@example.com
john@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Usernames&lt;/li&gt;
&lt;li&gt;External IDs&lt;/li&gt;
&lt;li&gt;API tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rails Validation vs Database Constraint
&lt;/h3&gt;

&lt;p&gt;Many developers write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not enough.&lt;/p&gt;

&lt;p&gt;Always add a database unique index because validations can be bypassed during race conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Composite (Multi-column) Index
&lt;/h3&gt;

&lt;p&gt;Used when queries filter using multiple columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important Rule
&lt;/h3&gt;

&lt;p&gt;Index order matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works efficiently for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But not for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In e-commerce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Partial Index
&lt;/h3&gt;

&lt;p&gt;Indexes only a subset of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where: &lt;/span&gt;&lt;span class="s2"&gt;"active = true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use It?
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 million users
9 million inactive 
1 million active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an index only for active users makes the index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller&lt;/li&gt;
&lt;li&gt;Faster&lt;/li&gt;
&lt;li&gt;Less memory intensive&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Uses
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Soft deletes&lt;/li&gt;
&lt;li&gt;Active records&lt;/li&gt;
&lt;li&gt;Published content&lt;/li&gt;
&lt;li&gt;Pending jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Index with Order
&lt;/h3&gt;

&lt;p&gt;Optimizes sorting operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;created_at: :desc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;News feeds&lt;/li&gt;
&lt;li&gt;Activity logs&lt;/li&gt;
&lt;li&gt;Product listings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. GIN Index
&lt;/h3&gt;

&lt;p&gt;GIN stands for Generalized Inverted Index.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;JSONB&lt;/li&gt;
&lt;li&gt;Arrays&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSONB Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:preferences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;preferences&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{"theme":"dark"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Rails Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"preferences @&amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;theme: &lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without GIN indexes, JSONB searches become slow as data grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Full Text Search Index
&lt;/h3&gt;

&lt;p&gt;PostgreSQL can act as a search engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"to_tsvector('english', content)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_articles_on_content_search"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rails indexing'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Useful For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Blogs&lt;/li&gt;
&lt;li&gt;Documentation sites&lt;/li&gt;
&lt;li&gt;Product search&lt;/li&gt;
&lt;li&gt;Knowledge bases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Concurrent Index
&lt;/h3&gt;

&lt;p&gt;One of the most important production techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on a huge table can lock writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToUsersEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt; 
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No downtime&lt;/li&gt;
&lt;li&gt;No table lock&lt;/li&gt;
&lt;li&gt;Safe for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real World
&lt;/h3&gt;

&lt;p&gt;Never add indexes to large production tables without considering concurrent creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Expression Index
&lt;/h3&gt;

&lt;p&gt;Indexes a calculated value instead of a column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"LOWER(email) = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"LOWER(email)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_users_on_lower_email"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Supports fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case-insensitive searches&lt;/li&gt;
&lt;li&gt;Date transformations&lt;/li&gt;
&lt;li&gt;String manipulations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Covering Index (INCLUDE)
&lt;/h3&gt;

&lt;p&gt;Introduced to reduce table lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;include: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Useful?
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL can answer directly from the index.&lt;/p&gt;

&lt;p&gt;This is called an &lt;strong&gt;Index Only Scan&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fewer disk reads&lt;/li&gt;
&lt;li&gt;Faster queries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  11. BRIN Index
&lt;/h3&gt;

&lt;p&gt;BRIN stands for Block Range Index.&lt;/p&gt;

&lt;p&gt;Designed for huge tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :brin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Analytics data&lt;/li&gt;
&lt;li&gt;Audit records&lt;/li&gt;
&lt;li&gt;Time-series events&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Instead of storing every value, BRIN stores summaries for ranges of pages.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely small indexes&lt;/li&gt;
&lt;li&gt;Very low maintenance overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;A BRIN index may be only a few MBs compared to hundreds of MBs for a B-tree index.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Hash Index
&lt;/h3&gt;

&lt;p&gt;Optimized for equality lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :hash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitation
&lt;/h3&gt;

&lt;p&gt;Does not support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;
&amp;gt;
BETWEEN
ORDER BY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because B-tree indexes support more operations, Hash indexes are rarely used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Index Should You Choose?
&lt;/h3&gt;

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

&lt;p&gt;Indexes are not about adding them everywhere.&lt;/p&gt;

&lt;p&gt;A good database engineer first identifies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slow queries&lt;/li&gt;
&lt;li&gt;Query patterns&lt;/li&gt;
&lt;li&gt;Filter columns&lt;/li&gt;
&lt;li&gt;Sort columns&lt;/li&gt;
&lt;li&gt;Join columns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then chooses the index that matches the workload.&lt;/p&gt;

&lt;p&gt;Always verify improvements using:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and remember that every index speeds up reads but adds overhead to writes. The best indexing strategy is the one that balances both.&lt;/p&gt;

&lt;p&gt;Concurrent Indexes: &lt;a href="https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production" rel="noopener noreferrer"&gt;https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/postgresql-index-types-explained-with-real-rails-examples" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 1, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>performance</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Understanding Indexes Before Learning Index Types</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 01 Jul 2026 07:59:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</guid>
      <description>&lt;p&gt;Before diving into different types of indexes, let’s understand why indexes are needed.&lt;/p&gt;

&lt;p&gt;Imagine a library with 100,000 books.&lt;/p&gt;

&lt;p&gt;If someone asks for a book named &lt;em&gt;“Clean Code”&lt;/em&gt;, there are two ways to find it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Without an Index
&lt;/h3&gt;

&lt;p&gt;You start from the first shelf and check every book one by one until you find the required book.&lt;/p&gt;

&lt;p&gt;This approach is similar to a &lt;strong&gt;Sequential Scan&lt;/strong&gt; in PostgreSQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Clean Code'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL may need to scan every row in the table to find matching records.&lt;/p&gt;

&lt;p&gt;As the table grows from thousands to millions of rows, query performance degrades significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an Index
&lt;/h3&gt;

&lt;p&gt;Now imagine the library has a catalog that stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Clean Code → Shelf A12
The Pragmatic Programmer → Shelf B03
Refactoring → Shelf C08
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of checking every book, you first look at the catalog and immediately jump to the correct shelf.&lt;/p&gt;

&lt;p&gt;This catalog is similar to a database index.&lt;/p&gt;

&lt;p&gt;An index stores selected column values in a structure that allows PostgreSQL to locate rows much faster than scanning the entire table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Index Every Column?
&lt;/h3&gt;

&lt;p&gt;A common beginner question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If indexes make queries faster, why not create indexes everywhere?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indexes improve read performance, but they come with a cost.&lt;/p&gt;

&lt;p&gt;Whenever data changes, PostgreSQL must update both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The table data&lt;/li&gt;
&lt;li&gt;Every related index&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the users table has 10 indexes, PostgreSQL must update all 10 indexes during the insert operation.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More storage usage&lt;/li&gt;
&lt;li&gt;Slower INSERT operations&lt;/li&gt;
&lt;li&gt;Slower UPDATE operations&lt;/li&gt;
&lt;li&gt;Slower DELETE operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of this trade-off, indexes should be added only for queries that are frequently executed and benefit from faster lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Uses an Index
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using index_users_on_email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database can jump directly to matching records instead of examining every row.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Rule
&lt;/h3&gt;

&lt;p&gt;Create an index when a column is frequently used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WHERE clauses&lt;/li&gt;
&lt;li&gt;JOIN conditions&lt;/li&gt;
&lt;li&gt;ORDER BY clauses&lt;/li&gt;
&lt;li&gt;GROUP BY clauses&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are strong candidates for indexing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Verify an Index Is Being Used
&lt;/h3&gt;

&lt;p&gt;Never assume PostgreSQL is using your index.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;This confirms PostgreSQL is benefiting from the index.&lt;/p&gt;

&lt;p&gt;Understanding these fundamentals makes it much easier to choose the right indexing strategy, whether it is a simple B-Tree index, a composite index, a GIN index for JSONB data, or a BRIN index for massive datasets.&lt;/p&gt;

</description>
      <category>indexes</category>
      <category>rails</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <item>
      <title>PostgreSQL Concurrent Indexes in Rails: Avoiding Downtime in Production</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 18 Jun 2026 07:02:08 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production-7mf</link>
      <guid>https://dev.to/yashika_vijayvargiya/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production-7mf</guid>
      <description>&lt;p&gt;When working with large production databases, adding an index may seem like a simple migration.&lt;/p&gt;

&lt;p&gt;However, on tables with millions of records, a regular index creation can lock writes and impact your application's availability.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore what concurrent indexes are, why they matter, and how to safely create them in Ruby on Rails.&lt;/p&gt;

&lt;p&gt;The Problem with Normal Index Creation&lt;/p&gt;

&lt;p&gt;Suppose we have a large users table with millions of records.&lt;/p&gt;

&lt;p&gt;A typical Rails migration might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, PostgreSQL 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;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;index_users_on_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While PostgreSQL builds the index, it acquires locks on the table.&lt;/p&gt;

&lt;p&gt;On large tables this can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Slower API responses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked INSERT operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked UPDATE operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked DELETE operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potential downtime during deployment&lt;/p&gt;&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;users table = 50 million rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an index on such a table can take several minutes, and during that time write operations may be affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution: Concurrent Indexes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL provides a safer alternative:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CREATE INDEX CONCURRENTLY&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This allows PostgreSQL to build the index while minimizing disruption to ongoing database operations.&lt;/p&gt;

&lt;p&gt;In Rails, this can be achieved using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToUsersEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How Concurrent Indexes Work&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal Index Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lock table&lt;br&gt;
↓&lt;br&gt;
Scan rows&lt;br&gt;
↓&lt;br&gt;
Build index&lt;br&gt;
↓&lt;br&gt;
Unlock table&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrent Index Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scan table&lt;br&gt;
↓&lt;br&gt;
Build index in background&lt;br&gt;
↓&lt;br&gt;
Track ongoing changes&lt;br&gt;
↓&lt;br&gt;
Finalize index&lt;/p&gt;

&lt;p&gt;The application continues to serve requests while the index is being created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;disable_ddl_transaction!&lt;/code&gt; Is Required&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common mistake is forgetting:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;disable_ddl_transaction!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL does not allow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Rails wraps migrations in a transaction by default, so we must disable it before creating concurrent indexes.&lt;/p&gt;

&lt;p&gt;Without it, Rails raises an error during migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complete Rails Example&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;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToBookingsUserId&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.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;

    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When Should You Use Concurrent Indexes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concurrent indexes are recommended when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The table contains millions of rows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application is live&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downtime is unacceptable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The database receives frequent writes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical examples include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Orders table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bookings table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactions table&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When Is a Regular Index Fine?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For smaller tables and development environments:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;add_index :users, :email&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;is usually sufficient.&lt;/p&gt;

&lt;p&gt;Since concurrent index creation performs additional work, it is generally slower than normal index creation.&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%2F9qdem8rvvwfcli5w0kvk.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%2F9qdem8rvvwfcli5w0kvk.png" alt=" " width="749" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a booking platform with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bookings table = 100 million rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A slow query is identified:&lt;br&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;Booking&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;hotel_id: &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;:hotel_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The obvious fix 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;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hotel_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a normal index creation during business hours could affect booking APIs.&lt;/p&gt;

&lt;p&gt;Instead, deploying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hotel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;allows the application to remain available while the index is built.&lt;/p&gt;

&lt;p&gt;Checking Existing Indexes&lt;/p&gt;

&lt;p&gt;To view indexes in PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_indexes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from the PostgreSQL console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interview Question:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why do we use concurrent indexes in Rails?&lt;/p&gt;

&lt;p&gt;A good answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Normal index creation can lock writes on large tables and impact application availability. PostgreSQL provides CREATE INDEX CONCURRENTLY to build indexes with minimal locking. In Rails, this is achieved using algorithm: :concurrently along with disable_ddl_transaction!. Although concurrent indexes take longer to build, they are much safer for production systems with large datasets and high traffic&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Creating indexes is one of the easiest ways to improve query performance, but doing it incorrectly in production can introduce downtime.&lt;/p&gt;

&lt;p&gt;Whenever you’re adding indexes to large, high-traffic tables, consider using concurrent indexes. The migration may take longer to complete, but your users won’t experience disruptions while it runs.&lt;/p&gt;

&lt;p&gt;A few extra minutes during deployment are often worth avoiding minutes of downtime in production.&lt;/p&gt;

</description>
      <category>indexing</category>
      <category>performance</category>
      <category>database</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Rails Performance Optimization: Fixing N+1 Queries with includes, preload, and eager_load</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 10 Jun 2026 10:22:00 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/rails-performance-optimization-fixing-n1-queries-with-includes-preload-and-eagerload-4bca</link>
      <guid>https://dev.to/yashika_vijayvargiya/rails-performance-optimization-fixing-n1-queries-with-includes-preload-and-eagerload-4bca</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on Hashnode:&lt;br&gt;
&lt;a href="https://railswithyashika.hashnode.dev/rails-performance-n-plus-one-queries" rel="noopener noreferrer"&gt;https://railswithyashika.hashnode.dev/rails-performance-n-plus-one-queries&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When working with associations in Rails, it's easy to accidentally introduce performance issues. One of the most common problems is the N+1 Query Problem.&lt;/p&gt;

&lt;p&gt;In this article, we'll understand what N+1 queries are, how they impact performance, and the differences between includes, preload, and eager_load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is an N+1 Query?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose we have the following models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:posts&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;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We fetch all posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And display the author's name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@posts&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;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queries Generated&lt;br&gt;
Rails first loads all posts:&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for each post:&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="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&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;LIMIT&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;LIMIT&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;If there are 100 posts, Rails executes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query for posts&lt;/li&gt;
&lt;li&gt;100 queries for users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;101 queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is called the N+1 query problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is it a Problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As the amount of data grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database load increases&lt;/li&gt;
&lt;li&gt;Response times become slower&lt;/li&gt;
&lt;li&gt;More memory and CPU are consumed&lt;/li&gt;
&lt;li&gt;Application scalability decreases
A page that works fine with 10 records can become painfully slow with 1,000 records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixing N+1 Queries with includes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The simplest solution 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="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails 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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;;&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only 2 queries are executed regardless of how many posts exist.&lt;/p&gt;

&lt;p&gt;Understanding includes&lt;/p&gt;

&lt;p&gt;Most Rails developers use includes, but many don't know how it actually works.&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="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated Queries&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails loads records using separate queries and associates them in memory.&lt;/p&gt;

&lt;p&gt;When Rails Converts includes into JOIN&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="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Rails generates:&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="n"&gt;posts&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;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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 query references the users table, Rails automatically switches to a JOIN strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding preload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; always loads associations using separate queries.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Queries Generated&lt;/strong&gt;&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;IN&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the generated SQL is similar to &lt;code&gt;includes&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Key Difference&lt;/strong&gt;&lt;br&gt;
Unlike &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; never converts into a JOIN.&lt;br&gt;
This will fail:&lt;br&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;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;missing FROM-clause entry for table "users"

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

&lt;/div&gt;



&lt;p&gt;Since no JOIN is generated, Rails cannot reference columns from the users table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Use preload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;preload&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know separate queries are preferred.&lt;/li&gt;
&lt;li&gt;You only want to avoid N+1 queries.&lt;/li&gt;
&lt;li&gt;You don't need conditions on associated tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understanding eager_load&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eager_load&lt;/code&gt; always uses a &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query Generated&lt;/strong&gt;&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="n"&gt;posts&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;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;users&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;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is fetched in a single query.&lt;/p&gt;

&lt;p&gt;Filtering on Associated Tables&lt;br&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;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="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;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generated SQL:&lt;/strong&gt;&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="nv"&gt;"posts"&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="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_id"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This works because the users table is already joined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;includes vs preload vs eager_load&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Queries&lt;/th&gt;
&lt;th&gt;Uses JOIN&lt;/th&gt;
&lt;th&gt;Can Filter Associated Table&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;includes&lt;/td&gt;
&lt;td&gt;Usually 2&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;preload&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eager_load&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Always&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Which One Should You Use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Use includes&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;code&gt;Post.includes(:user)&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Default choice for most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use preload&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you explicitly want separate queries and no JOIN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use eager_load&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;When filtering, ordering, or searching on associated tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detecting N+1 Queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few ways to identify N+1 problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check Development Logs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look for repeated queries being executed inside loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Bullet Gem&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'bullet'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bullet will notify you whenever an N+1 query is detected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Monitoring Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scout APM&lt;/li&gt;
&lt;li&gt;New Relic&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help identify slow database queries in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;N+1 queries are one of the most common performance issues in Rails applications.&lt;/p&gt;

&lt;p&gt;Understanding the differences between includes, preload, and eager_load can help you write more efficient database queries and build scalable applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a rule of thumb:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with &lt;code&gt;includes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;preload&lt;/code&gt; when you want guaranteed separate queries&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;eager_load&lt;/code&gt; when you need JOIN-based filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few minutes spent analyzing your SQL queries can save hours of performance troubleshooting later.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>performance</category>
      <category>database</category>
    </item>
  </channel>
</rss>
