<?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>What Is Vibe Coding? Benefits, Risks, and Best Practices for AI-Assisted Development</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:00:59 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/what-is-vibe-coding-benefits-risks-and-best-practices-for-ai-assisted-development-36ca</link>
      <guid>https://dev.to/yashika_vijayvargiya/what-is-vibe-coding-benefits-risks-and-best-practices-for-ai-assisted-development-36ca</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI has changed how developers write software.&lt;/p&gt;

&lt;p&gt;Instead of writing every line of code manually, many developers now describe what they want in plain English and let AI generate the implementation.&lt;/p&gt;

&lt;p&gt;This way of working has become popularly known as &lt;code&gt;vibe coding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some developers see it as the future of software development, while others worry it encourages developers to rely too heavily on AI.&lt;/p&gt;

&lt;p&gt;So, what exactly is vibe coding, and when should you use it?&lt;/p&gt;

&lt;p&gt;Let's explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Vibe Coding?
&lt;/h2&gt;

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

&lt;p&gt;Vibe coding is an AI-assisted development approach where developers describe what they want to build instead of manually writing every line of code.&lt;/p&gt;

&lt;p&gt;Rather than starting with an empty file, developers provide prompts such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a REST API for managing products with authentication and pagination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI generates the initial implementation, which the developer reviews, modifies, and tests.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Instead of writing every line yourself, you guide AI to write the first draft of your code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Traditional Coding vs. Vibe Coding
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Traditional Development&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Understand the problem&lt;br&gt;
↓&lt;br&gt;
Design the solution&lt;br&gt;
↓&lt;br&gt;
Write code&lt;br&gt;
↓&lt;br&gt;
Debug&lt;br&gt;
↓&lt;br&gt;
Test&lt;br&gt;
↓&lt;br&gt;
Deploy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The developer writes every implementation detail manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Coding
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Understand the problem&lt;br&gt;
↓&lt;br&gt;
Describe it to AI&lt;br&gt;
↓&lt;br&gt;
AI generates code&lt;br&gt;
↓&lt;br&gt;
Review&lt;br&gt;
↓&lt;br&gt;
Test&lt;br&gt;
↓&lt;br&gt;
Improve&lt;br&gt;
↓&lt;br&gt;
Deploy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The developer focuses more on problem-solving and less on typing boilerplate code.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Example
&lt;/h2&gt;

&lt;p&gt;Imagine you want to build authentication in a Rails application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional approach&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Generate models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure routes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add controllers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write validations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement sessions or JWT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create tests&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This might take hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vibe coding approach&lt;/strong&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Create JWT authentication for a Rails API with login, logout, refresh tokens, request specs, and proper error handling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within seconds, the AI generates the initial implementation.&lt;/p&gt;

&lt;p&gt;Your job becomes reviewing, testing, and refining the code instead of writing everything from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is Vibe Coding Becoming Popular?
&lt;/h2&gt;

&lt;p&gt;Modern AI coding tools can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understand multiple files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate large codebases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactor code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explain existing code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Detect common bugs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows developers to move faster, especially when working on repetitive tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Popular AI Coding Tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some of the most widely used AI coding assistants include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cursor&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Claude Code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub Copilot&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemini Code Assist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Windsurf&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help developers write, refactor, and understand code more efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits of Vibe Coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faster Development&lt;/strong&gt;&lt;br&gt;
Instead of spending time writing repetitive code, developers can focus on solving business problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Less Boilerplate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI can generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CRUD operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Easier Learning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers exploring a new language or framework can use AI to understand patterns and best practices more quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better Productivity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many developers use AI for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Writing tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explaining unfamiliar code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring methods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generating documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating migration files&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Risks of Vibe Coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Despite its advantages, vibe coding has important limitations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Can Produce Incorrect Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI-generated code isn't always correct.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Misuse libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ignore edge cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Introduce bugs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate outdated patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always review the generated code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security Risks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI might generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SQL injection vulnerabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weak authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insecure API implementations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security-critical code should always be reviewed carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overengineering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes AI solves simple problems with unnecessarily complex code.&lt;/p&gt;

&lt;p&gt;Good developers know when a simpler solution is better.&lt;/p&gt;

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

&lt;p&gt;If developers accept AI-generated code without understanding it, debugging and maintaining the application becomes much harder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When Should You Use Vibe Coding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vibe coding works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CRUD applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API scaffolding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refactoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code explanations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repetitive development tasks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When Should You Avoid It?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Be cautious when working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Payment systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authorization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Financial calculations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security-sensitive features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Distributed systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance-critical code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These areas require careful design, testing, and review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use AI as a collaborator&lt;br&gt;
Don't treat AI as a replacement for engineering judgment.&lt;/p&gt;

&lt;p&gt;Review every generated change&lt;br&gt;
Understand what the AI generated before merging it.&lt;/p&gt;

&lt;p&gt;Write tests&lt;br&gt;
AI-generated code should be validated with automated tests.&lt;/p&gt;

&lt;p&gt;Keep prompts specific&lt;br&gt;
Instead of asking:&lt;/p&gt;

&lt;p&gt;Build an API.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;Build a Rails API using service objects, request specs, pagination, JWT authentication, and proper error handling.&lt;/p&gt;

&lt;p&gt;Clear prompts generally produce better results.&lt;/p&gt;

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

&lt;p&gt;Vibe coding is changing the software development workflow by allowing developers to express ideas in natural language and use AI to generate an initial implementation.&lt;/p&gt;

&lt;p&gt;Used thoughtfully, it can improve productivity and reduce repetitive work.&lt;/p&gt;

&lt;p&gt;However, AI-generated code is still software that needs to be reviewed, tested, and maintained.&lt;/p&gt;

&lt;p&gt;The most effective developers aren't those who let AI write everything—they're the ones who know when to rely on AI and when to rely on their own expertise.&lt;/p&gt;

&lt;p&gt;Hashnode AI&lt;/p&gt;

&lt;p&gt;Hashnode AI is a Pro feature&lt;br&gt;
Upgrade to Pro to refine your drafts, suggest edits, and brainstorm with the Writing Assistant.&lt;/p&gt;

&lt;p&gt;Upgrade to Pro&lt;br&gt;
Search Hashnode&lt;br&gt;
Search posts, tags, users, and pages&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Rails Bug That Disappeared After a Server Restart — Until I Found the Mutation</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Mon, 17 Aug 2026 07:16:44 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/the-rails-bug-that-disappeared-after-a-server-restart-until-i-found-the-mutation-5bo6</link>
      <guid>https://dev.to/yashika_vijayvargiya/the-rails-bug-that-disappeared-after-a-server-restart-until-i-found-the-mutation-5bo6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;Some bugs are difficult because the code is complicated.&lt;/p&gt;

&lt;p&gt;Others are difficult because the bug seems to have no rules.&lt;/p&gt;

&lt;p&gt;This was the second kind.&lt;/p&gt;

&lt;p&gt;A Rails application I was working on started rendering ActiveAdmin menus incorrectly.&lt;/p&gt;

&lt;p&gt;Sometimes nested menu items appeared under the wrong level.&lt;/p&gt;

&lt;p&gt;Sometimes everything looked completely normal.&lt;/p&gt;

&lt;p&gt;And sometimes restarting the application made the problem disappear.&lt;/p&gt;

&lt;p&gt;That last part was the most suspicious.&lt;/p&gt;

&lt;p&gt;A restart shouldn't "fix" a deterministic piece of Ruby code.&lt;/p&gt;

&lt;p&gt;So I started digging.&lt;/p&gt;

&lt;p&gt;What I eventually found was a much more interesting problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An object was being modified somewhere I didn't expect it to be modified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the fix turned out to be one of the simplest Ruby changes I've made:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But getting there took considerably more work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The application was using ActiveAdmin for its administrative interface.&lt;/p&gt;

&lt;p&gt;The problem appeared in the navigation menu.&lt;/p&gt;

&lt;p&gt;We had nested menu items, and occasionally they were rendered as top-level items instead of being nested under their intended parent.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

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

Reports
  ├── Sales
  └── Revenue

Actual:

Reports
Sales
Revenue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frustrating part was that the behavior wasn't consistently reproducible.&lt;/p&gt;

&lt;p&gt;The same application could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;render the menu correctly&lt;/li&gt;
&lt;li&gt;render it incorrectly&lt;/li&gt;
&lt;li&gt;restart&lt;/li&gt;
&lt;li&gt;start working again&lt;/li&gt;
&lt;li&gt;continue working for some time&lt;/li&gt;
&lt;li&gt;then fail again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not the kind of bug where I could simply open the relevant controller and find a typo.&lt;/p&gt;

&lt;p&gt;It looked almost random.&lt;/p&gt;

&lt;h3&gt;
  
  
  The First Clue: Restarting the Server "Fixed" It
&lt;/h3&gt;

&lt;p&gt;The most interesting observation was that restarting the server temporarily resolved the problem.&lt;/p&gt;

&lt;p&gt;That immediately made me suspicious of &lt;strong&gt;state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If restarting the process changes the behavior without changing the database or application code, something may be surviving longer than expected inside the process.&lt;/p&gt;

&lt;p&gt;That led me away from asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What condition makes the menu render incorrectly?"&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What state is being mutated between requests?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was a much better question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following the Menu Construction
&lt;/h2&gt;

&lt;p&gt;I started tracing how ActiveAdmin constructs its menus.&lt;/p&gt;

&lt;p&gt;Eventually I reached the menu node implementation and the &lt;code&gt;add&lt;/code&gt; method responsible for adding menu items.&lt;/p&gt;

&lt;p&gt;The important part looked roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# menu item construction&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, there was nothing obviously wrong.&lt;/p&gt;

&lt;p&gt;The method received an &lt;code&gt;options&lt;/code&gt; object and used it to construct the menu node.&lt;/p&gt;

&lt;p&gt;But the more I followed the execution path, the more suspicious the object itself became.&lt;/p&gt;

&lt;p&gt;The same options object was being passed through multiple parts of the menu-building process.&lt;/p&gt;

&lt;p&gt;And something was changing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Wasn't the Value
&lt;/h2&gt;

&lt;p&gt;This was the key realization.&lt;/p&gt;

&lt;p&gt;The problem wasn't necessarily that the options contained the wrong value.&lt;/p&gt;

&lt;p&gt;The problem was that &lt;strong&gt;the options object itself was mutable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine this simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;label: &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You pass that object into another method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;build_menu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;build_menu&lt;/code&gt; modifies it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the original object has now changed too.&lt;/p&gt;

&lt;p&gt;There is no copy.&lt;/p&gt;

&lt;p&gt;There is no isolation.&lt;/p&gt;

&lt;p&gt;Both pieces of code are holding a reference to the same object.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even though the code that originally created the options never intentionally removed it.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of mutation that can create a bug that looks random.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Looked Random
&lt;/h2&gt;

&lt;p&gt;The menu wasn't necessarily broken every time.&lt;/p&gt;

&lt;p&gt;The behavior depended on &lt;strong&gt;when and how the shared object was mutated&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That made the symptoms particularly confusing.&lt;/p&gt;

&lt;p&gt;A simplified lifecycle looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create menu options
        ↓
Pass options to ActiveAdmin
        ↓
ActiveAdmin modifies options
        ↓
Original object is now different
        ↓
Another menu operation uses it
        ↓
Parent information is missing/incorrect
        ↓
Nested item becomes top-level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because the object lived inside the running Ruby process, restarting the server cleared that in-memory state.&lt;/p&gt;

&lt;p&gt;That explained why a restart could appear to "fix" the bug.&lt;/p&gt;

&lt;p&gt;It wasn't fixing anything.&lt;/p&gt;

&lt;p&gt;It was simply giving us a fresh process with fresh objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I Found the GitHub Issue
&lt;/h2&gt;

&lt;p&gt;After spending a significant amount of time debugging the behavior at the application and dependency level, I searched GitHub for similar ActiveAdmin problems.&lt;/p&gt;

&lt;p&gt;That's when I found:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ActiveAdmin issue #8078 — "Nested menu items are rendered top-level."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The issue description was remarkably similar.&lt;/p&gt;

&lt;p&gt;It reported that nested menus were sometimes rendered at the top level, and that restarting the server temporarily solved the problem. The issue was difficult to reproduce and had been observed in production.&lt;/p&gt;

&lt;p&gt;That was the moment when the investigation changed direction.&lt;/p&gt;

&lt;p&gt;I wasn't dealing with some mysterious Rails rendering bug.&lt;/p&gt;

&lt;p&gt;There was already evidence that ActiveAdmin's menu construction could be modifying menu state.&lt;/p&gt;

&lt;p&gt;The issue eventually led to ActiveAdmin PR #8132, titled:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Make sure menu creation does not modify menu options."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ActiveAdmin maintainer later reported that the patch had run in production for almost a month without the menu rendering problem recurring before merging it.&lt;/p&gt;

&lt;p&gt;Now I had a much stronger hypothesis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The options object was being mutated during menu construction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next question was:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I prevent that mutation from affecting the object my application is using?&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Fix: Give &lt;code&gt;add&lt;/code&gt; Its Own Copy
&lt;/h3&gt;

&lt;p&gt;Instead of passing the original options object into the existing implementation, I created a duplicate first.&lt;/p&gt;

&lt;p&gt;The patch was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MenuNode&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;MenuNode&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    │
    ▼
options ───────────────┐
                      │
                      ▼
                ActiveAdmin
                      │
                      ▼
                modifies object
                      │
                      ▼
            original options changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    │
    ▼
original options

    │
    │ dup
    ▼

copied options
    │
    ▼
ActiveAdmin
    │
    ▼
can modify its copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original object remains untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;dup&lt;/code&gt; Fixed It
&lt;/h2&gt;

&lt;p&gt;Ruby objects are references.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;label: &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;copy&lt;/code&gt; isn't a new Hash.&lt;/p&gt;

&lt;p&gt;Both variables point to the same object.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;also changes:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;because they're the same object.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;creates a separate Hash.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't remove the key from the original &lt;code&gt;options&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's exactly the isolation we needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Used &lt;code&gt;super&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There was another important detail in the fix.&lt;/p&gt;

&lt;p&gt;I didn't want to reimplement ActiveAdmin's &lt;code&gt;add&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;That would have created another maintenance problem.&lt;/p&gt;

&lt;p&gt;Instead, I wanted to change &lt;strong&gt;one thing&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give the original method a safe copy of the options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then let ActiveAdmin continue doing everything else exactly as it already did.&lt;/p&gt;

&lt;p&gt;That's why the implementation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Our wrapper
    ↓
duplicate options
    ↓
ActiveAdmin's original add
    ↓
existing behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a useful pattern when working with a third-party library:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the boundary, not the library's internal behavior.&lt;/strong&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Debugging Lesson: Don't Trust "Random"
&lt;/h3&gt;

&lt;p&gt;One of the biggest lessons from this bug was that "random" doesn't necessarily mean random.&lt;/p&gt;

&lt;p&gt;When an application behaves differently after a restart, there is often state involved.&lt;/p&gt;

&lt;p&gt;That state could live in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;class variables&lt;/li&gt;
&lt;li&gt;global objects&lt;/li&gt;
&lt;li&gt;memoized values&lt;/li&gt;
&lt;li&gt;caches&lt;/li&gt;
&lt;li&gt;singleton instances&lt;/li&gt;
&lt;li&gt;mutable configuration&lt;/li&gt;
&lt;li&gt;shared Hashes or Arrays&lt;/li&gt;
&lt;li&gt;library objects that survive across requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Ruby, mutable objects make this especially important.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_options&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;look similar.&lt;/p&gt;

&lt;p&gt;But they create very different ownership semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dependency Was Part of the Application
&lt;/h2&gt;

&lt;p&gt;Another lesson was about debugging third-party libraries.&lt;/p&gt;

&lt;p&gt;It is tempting to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The bug is in ActiveAdmin."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that doesn't help when you're responsible for the application.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where does the incorrect state enter my application?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That led me through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application behavior
       ↓
ActiveAdmin
       ↓
Menu construction
       ↓
MenuNode#add
       ↓
options object
       ↓
unexpected mutation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I could see the mutation boundary, the fix became straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix Was Tiny. The Investigation Wasn't.
&lt;/h2&gt;

&lt;p&gt;This is something I've noticed repeatedly when debugging production Rails applications.&lt;/p&gt;

&lt;p&gt;The final patch often looks deceptively simple.&lt;/p&gt;

&lt;p&gt;In this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is almost trivial.&lt;/p&gt;

&lt;p&gt;But the path to that line involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reproducing an intermittent problem&lt;/li&gt;
&lt;li&gt;comparing behavior before and after a restart&lt;/li&gt;
&lt;li&gt;tracing ActiveAdmin internals&lt;/li&gt;
&lt;li&gt;inspecting how menu nodes were constructed&lt;/li&gt;
&lt;li&gt;questioning object ownership&lt;/li&gt;
&lt;li&gt;searching existing GitHub issues&lt;/li&gt;
&lt;li&gt;finding a closely related ActiveAdmin issue&lt;/li&gt;
&lt;li&gt;understanding why the existing implementation could mutate shared state&lt;/li&gt;
&lt;li&gt;introducing a minimal wrapper&lt;/li&gt;
&lt;li&gt;delegating back to the original implementation with &lt;code&gt;super&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difficulty wasn't writing the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The difficulty was finding the right layer to fix.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Taught Me About Ruby
&lt;/h2&gt;

&lt;p&gt;Ruby makes it very easy to pass objects around.&lt;/p&gt;

&lt;p&gt;That's one of its strengths.&lt;/p&gt;

&lt;p&gt;But that convenience also means that mutable objects can cross boundaries without making ownership obvious.&lt;/p&gt;

&lt;p&gt;Whenever I see a method receiving a Hash or Array, I now ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who owns this object, and is this method allowed to modify it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer isn't clear, copying the object at the boundary can sometimes be the difference between stable behavior and an intermittent production bug.&lt;/p&gt;

&lt;p&gt;Of course, &lt;code&gt;dup&lt;/code&gt; isn't a universal solution.&lt;/p&gt;

&lt;p&gt;It is a shallow copy, so nested mutable objects can still be shared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;menu: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;parent: &lt;/span&gt;&lt;span class="s2"&gt;"Reports"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that situation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;duplicates the outer Hash, but the nested &lt;code&gt;:menu&lt;/code&gt; Hash is still shared.&lt;/p&gt;

&lt;p&gt;So the correct solution depends on what the called code actually mutates.&lt;/p&gt;

&lt;p&gt;In this case, duplicating the options object at the &lt;code&gt;add&lt;/code&gt; boundary was enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;When a bug looks random, don't immediately assume the framework is behaving unpredictably.&lt;/p&gt;

&lt;p&gt;Look for state.&lt;/p&gt;

&lt;p&gt;When restarting the server makes the bug disappear, don't celebrate.&lt;/p&gt;

&lt;p&gt;Ask what state the restart destroyed.&lt;/p&gt;

&lt;p&gt;When a dependency receives a mutable object, don't assume it will leave it untouched.&lt;/p&gt;

&lt;p&gt;And when you finally identify the problem, resist the temptation to rewrite everything.&lt;/p&gt;

&lt;p&gt;Sometimes the safest fix is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dup&lt;/span&gt;
&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tiny boundary.&lt;/p&gt;

&lt;p&gt;A separate object.&lt;/p&gt;

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

&lt;p&gt;And suddenly the "random" bug isn't random anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The most valuable part of debugging isn't always the final code change.&lt;/p&gt;

&lt;p&gt;Sometimes it's the moment when a confusing symptom finally becomes a predictable consequence of one small assumption.&lt;/p&gt;

&lt;p&gt;In my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Menus randomly render incorrectly"
              ↓
"Restarting fixes it"
              ↓
"There must be state"
              ↓
"ActiveAdmin is modifying menu options"
              ↓
"The same mutable object is being reused"
              ↓
"Give ActiveAdmin a copy"
              ↓
options.dup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final fix was one line.&lt;/p&gt;

&lt;p&gt;The investigation was the real engineering work.&lt;/p&gt;

&lt;p&gt;And that's probably the part of this bug I'll remember.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>activeadmin</category>
      <category>opensource</category>
    </item>
    <item>
      <title>When a String Assumption Broke Newsletter Tag Filtering in Ruby</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Fri, 14 Aug 2026 10:55:37 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/when-a-string-assumption-broke-newsletter-tag-filtering-in-ruby-3ak3</link>
      <guid>https://dev.to/yashika_vijayvargiya/when-a-string-assumption-broke-newsletter-tag-filtering-in-ruby-3ak3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Single Type Assumption Broke Newsletter Tag Filtering in Ruby
&lt;/h2&gt;

&lt;p&gt;A bug doesn't always need hundreds of lines of code to cause a real problem.&lt;/p&gt;

&lt;p&gt;Sometimes, one assumption is enough.&lt;/p&gt;

&lt;p&gt;In this case, the assumption was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Every tag is a String."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That assumption lived inside a &lt;code&gt;Ruby filtering method&lt;/code&gt; in the open-source RubyEvents project.&lt;/p&gt;

&lt;p&gt;It worked perfectly — until a tag wasn't a String.&lt;/p&gt;

&lt;p&gt;That small mismatch was enough to turn a normal user interaction into an &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the story of how the bug was investigated, what was actually going wrong, and how a small change made the filtering logic safer and clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bug&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  "Cannot Click tags on Newsletter"
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Open a newsletter.&lt;/li&gt;
&lt;li&gt;Click one of its tags.&lt;/li&gt;
&lt;li&gt;Instead of getting the filtered announcements, the application raises an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue was tracked as &lt;strong&gt;#1836&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first glance, this sounds like it could be a routing or controller problem.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;The failure was deeper in the &lt;code&gt;tag-filtering logic&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;The filtering logic lived in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Announcement&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="c1"&gt;#by_tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original implementation was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intent is easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take all tags from an announcement&lt;/li&gt;
&lt;li&gt;convert them to lowercase&lt;/li&gt;
&lt;li&gt;compare them with the requested tag&lt;/li&gt;
&lt;li&gt;return matching announcements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For normal String values, this works perfectly.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;["Ruby", "Rails"].map(&amp;amp;:downcase)
&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is an important assumption hidden inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every element must respond to &lt;strong&gt;downcase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's not necessarily true.&lt;/p&gt;

&lt;p&gt;Imagine the collection contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Ruby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;eventually tries to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And integers don't have a &lt;code&gt;downcase&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The result is a &lt;strong&gt;NoMethodError&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So the real problem wasn't the newsletter link itself.&lt;/p&gt;

&lt;p&gt;The newsletter was simply the path that exposed an unsafe assumption in the &lt;code&gt;tag-filtering code&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;There were several ways this could have been "&lt;code&gt;fixed&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;For example, we could have assumed that all tags should always be Strings and modified the data at the source.&lt;/p&gt;

&lt;p&gt;But that would increase the scope of the change.&lt;/p&gt;

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

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

&lt;p&gt;So instead of changing the data source, I wanted to make the comparison resilient to the values it actually received.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The implementation became:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two important changes here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;to_s Creates a Safe Comparison Boundary&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;we explicitly convert the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a String remains a String:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;"Ruby".to_s
# =&amp;gt; "Ruby"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a non-String value becomes safely comparable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb_output"&gt;&lt;code&gt;&lt;span class="go"&gt;123.to_s
# =&amp;gt; "123"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filtering code no longer crashes simply because a tag value isn't already a String.&lt;/p&gt;

&lt;p&gt;This is a small example of something I find important when working with Ruby:&lt;/p&gt;

&lt;p&gt;Don't make an assumption about the type of an object if you don't actually need that assumption.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;casecmp? Expresses the Actual Requirement&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original implementation lowercased both sides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;But the actual requirement isn't:&lt;/p&gt;

&lt;p&gt;Convert everything to lowercase.&lt;/p&gt;

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

&lt;p&gt;Compare these values without considering case.&lt;/p&gt;

&lt;p&gt;Ruby provides exactly that operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;casecmp?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the comparison becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This makes the intent clearer.&lt;/p&gt;

&lt;p&gt;We're not transforming the data just to compare it.&lt;/p&gt;

&lt;p&gt;We're performing a case-insensitive comparison.&lt;/p&gt;

&lt;p&gt;Why &lt;code&gt;any?&lt;/code&gt; Instead of &lt;code&gt;map?&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This was another small but meaningful improvement.&lt;/p&gt;

&lt;p&gt;The old implementation transformed every tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;But we don't actually need a new array.&lt;/p&gt;

&lt;p&gt;We only need to answer one question:&lt;/p&gt;

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

&lt;p&gt;That's exactly what any? communicates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;It also allows Ruby to stop checking once a match is found.&lt;/p&gt;

&lt;p&gt;So the new implementation isn't simply more defensive.&lt;/p&gt;

&lt;p&gt;It's also closer to the actual intent of the operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before vs After&lt;/strong&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hidden assumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every tag
   ↓
must respond to #downcase

If one doesn't:

NoMethodError
   ↓
request fails
   ↓
user cannot follow the newsletter tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;by_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tag_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;casecmp?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tag value
   ↓
convert safely to String
   ↓
case-insensitive comparison
   ↓
match / no match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing filtering behavior remains intact while the unsafe type assumption is removed.&lt;/p&gt;

&lt;p&gt;Why This Bug Was Easy to Miss&lt;/p&gt;

&lt;p&gt;This is what I found most interesting about the issue.&lt;/p&gt;

&lt;p&gt;The original code isn't obviously bad.&lt;/p&gt;

&lt;p&gt;For a dataset containing only Strings, this is perfectly reasonable Ruby:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The problem only appears when the runtime data doesn't match the assumption made by the implementation.&lt;/p&gt;

&lt;p&gt;That's a common class of bugs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code assumption
       ↓
"this value will always be a String"
       ↓
Works for normal data
       ↓
Unexpected value enters the system
       ↓
Runtime failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson isn't:&lt;/p&gt;

&lt;p&gt;"Never use downcase."&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;Know where your assumptions about data types are coming from.&lt;/p&gt;

&lt;p&gt;If a method operates on data that can contain different types, the boundary where those values are consumed should be resilient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Investigation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What made this issue useful as a debugging exercise was that the final code change was small.&lt;/p&gt;

&lt;p&gt;The investigation was the interesting part.&lt;/p&gt;

&lt;p&gt;Instead of only looking at the error message, I traced the behavior back to the collection filtering logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That led to a few questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What type can each tag actually contain?&lt;/li&gt;
&lt;li&gt;Why does downcase fail?&lt;/li&gt;
&lt;li&gt;Do we really need to transform every tag?&lt;/li&gt;
&lt;li&gt;Can the comparison itself be made type-safe?&lt;/li&gt;
&lt;li&gt;Can the existing behavior be preserved without changing the surrounding code?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those questions were answered, the fix became much simpler.&lt;/p&gt;

&lt;p&gt;The problem wasn't complicated business logic.&lt;/p&gt;

&lt;p&gt;It was an unsafe assumption about the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Final Change&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual change was intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- Collection.new(select { |a| a.tags.map(&amp;amp;:downcase).include?(tag.downcase) })
&lt;/span&gt;&lt;span class="gi"&gt;+ Collection.new(select { |a| a.tags.any? { |tag_value| tag_value.to_s.casecmp?(tag) } })
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line changed.&lt;/p&gt;

&lt;p&gt;But that one line removed the assumption that every tag value was already a String.&lt;/p&gt;

&lt;p&gt;I also fixed a linting issue in a follow-up commit.&lt;/p&gt;

&lt;p&gt;The pull request was reviewed by the RubyEvents maintainer, passed all checks, and was merged into the project's main branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1836 — Cannot Click tags on Newsletter&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull Request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1847 — Fix tag filtering for non-string tags&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm Proud Of&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final diff is tiny.&lt;/p&gt;

&lt;p&gt;That's actually what I like about this fix.&lt;/p&gt;

&lt;p&gt;There was no need to rewrite the filtering system or introduce another abstraction.&lt;/p&gt;

&lt;p&gt;The existing behavior was already correct for valid String values.&lt;/p&gt;

&lt;p&gt;The problem was the assumption around those values.&lt;/p&gt;

&lt;p&gt;So instead of changing the entire flow, the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keeps the existing behavior&lt;/li&gt;
&lt;li&gt;makes the comparison type-safe&lt;/li&gt;
&lt;li&gt;avoids unnecessary array allocation&lt;/li&gt;
&lt;li&gt;expresses the intent with any?&lt;/li&gt;
&lt;li&gt;uses casecmp? for case-insensitive comparison&lt;/li&gt;
&lt;li&gt;keeps the change isolated to the filtering method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small change, focused responsibility.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Small bugs can expose bigger assumptions&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code wasn't complicated.&lt;/p&gt;

&lt;p&gt;The assumption behind the code was the real problem.&lt;/p&gt;

&lt;p&gt;Whenever I see code such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:some_method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I now ask myself:&lt;/p&gt;

&lt;p&gt;Do I know for certain that every item responds to this method?&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fix the behavior, not just the exception&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;But a good fix should do more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preserve existing behavior&lt;/li&gt;
&lt;li&gt;make the comparison safe&lt;/li&gt;
&lt;li&gt;make the intent clearer&lt;/li&gt;
&lt;li&gt;avoid unnecessary transformations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final implementation does all four.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Express the question you're actually asking&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The second version tells the reader what we're actually asking.&lt;/p&gt;

&lt;p&gt;We're not interested in producing another collection.&lt;/p&gt;

&lt;p&gt;We're asking whether any tag matches.&lt;/p&gt;

&lt;p&gt;That makes the code easier to reason about and maintain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open source debugging is different&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing I enjoy about contributing to open source is that you get to work with code outside the assumptions of your own applications.&lt;/p&gt;

&lt;p&gt;You don't necessarily know every historical decision behind a method.&lt;/p&gt;

&lt;p&gt;You don't know every shape of data that has passed through it.&lt;/p&gt;

&lt;p&gt;And you don't get to rewrite the whole system just because you find one imperfect assumption.&lt;/p&gt;

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

&lt;p&gt;That's what made this issue interesting to me.&lt;/p&gt;

&lt;p&gt;The final diff was tiny.&lt;/p&gt;

&lt;p&gt;The reasoning behind it was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A bug doesn't always announce itself with a complicated stack trace or a thousand-line fix.&lt;/p&gt;

&lt;p&gt;Sometimes it's hidden inside a single assumption:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"Every tag is a String."&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
When that assumption stopped being true, clicking a newsletter tag stopped working.&lt;/p&gt;

&lt;p&gt;The fix was to make the comparison type-safe, preserve case-insensitive matching, and express the filtering intent more directly.&lt;/p&gt;

&lt;p&gt;One line changed. One user-facing failure removed. One more reminder that robust software is often about handling the values we didn't expect.&lt;/p&gt;

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

&lt;p&gt;Issue:&lt;br&gt;
1836 — Cannot Click tags on Newsletter&lt;/p&gt;

&lt;p&gt;Pull Request:&lt;br&gt;
1847 — Fix tag filtering for non-string tags&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

VS Code + Ponytail
❌

VS Code + AI Agent
✅

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

&lt;/div&gt;



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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

Read Product

Version = 0

↓

Edit

↓

Save

Version becomes 1

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

Admin B

Read Product

Version = 0

↓

Edit

↓

Try Save

↓

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

&lt;/div&gt;



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

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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

rescue ActiveRecord::StaleObjectError

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

&lt;/div&gt;

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


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

&lt;/div&gt;

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


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

&lt;/div&gt;

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

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


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

&lt;/div&gt;


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

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

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


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

&lt;/div&gt;

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

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

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

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

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

&lt;/div&gt;



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

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



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

&lt;/div&gt;



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

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

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

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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

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

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

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



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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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



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

&lt;/div&gt;



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

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

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

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

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

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

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



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

&lt;/div&gt;



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

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

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

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



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

&lt;/div&gt;



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



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

&lt;/div&gt;



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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

</description>
      <category>indexes</category>
      <category>rails</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <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>
