<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Benito Serna</title>
  <subtitle>Tips and articles for Ruby on Rails developers</subtitle>
  <id>http://bhserna.com/</id>
  <link href="https://bhserna.com"/>
  <link href="https://bhserna.com/feed.xml" rel="self"/>
  <updated>2025-12-29T12:00:00Z</updated>
  <author>
    <name>Benito Serna</name>
  </author>
  <entry>
    <title>Fix n+1 queries on rails</title>
    <link rel="alternate" href="https://bhserna.com/fix-n+1-queries-on-rails"/>
    <id>https://bhserna.com/fix-n+1-queries-on-rails</id>
    <published>2023-06-22T00:01:00Z</published>
    <updated>2023-06-22T00:01:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;article class="center pv5 ph3 ph5-ns tc mb5 tc"&gt;
  &lt;h1 class="f3 f1-ns lh-title mb3"&gt;
    Fix n+1 queries on Rails
  &lt;/h1&gt;
  &lt;p class="fw4 f5 f4-ns lh-copy mt0 mb3 measure center"&gt;
    Learn different ways to solve n+1 queries beyond "includes", to be confident that you can handle tasks on complex data models and fix n+1 queries if they appear.
  &lt;/p&gt;
  &lt;p class="mt1"&gt;By &lt;a href="/" target="_blank" class="red"&gt;Benito Serna&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;

&lt;div class="bg-near-white pv5 ph3"&gt;
  &lt;div class="measure center lh-copy"&gt;
    &lt;h3&gt;Making things work isn't enough for you any more?&lt;/h3&gt;

    &lt;p&gt;Do you feel or maybe your boss have told you directly that now you need to consider performance and scalability?&lt;/p&gt;

    &lt;h3&gt;Do you feel unqualified to tackle complex tasks?&lt;/h3&gt;

    &lt;p&gt;Do you still have troubles fixing some n+1 queries?&lt;/p&gt;
    &lt;p&gt;Do you have problems trying to find why active record is ignoring your "includes"?&lt;/p&gt;

    &lt;h3&gt;Maybe you know the basics... &lt;/h3&gt;
    &lt;p&gt;Maybe you know how to fix some n+1 queries and use "preload" or "includes" here and there, but...&lt;/p&gt;

    &lt;h3&gt;Do you worry when they ask you for more?&lt;/h3&gt;
    &lt;p&gt;"Can you preload just an scope?"&lt;/p&gt;
    &lt;p&gt;"Can you change the implementation to preload via has many through?"&lt;/p&gt;
    &lt;p&gt;"Shoud we use a counter cache or just preload the counts?"&lt;/p&gt;
    &lt;p&gt;"Can you persist the calculation?... just please be aware of race conditions!"&lt;/p&gt;
    &lt;p&gt;"That fix to the n+1 queries, will hurt the performance... we need to try something else"&lt;/p&gt;

    &lt;h3&gt;But, what if you could...&lt;/h3&gt;

    &lt;p&gt;Ship code that fix user problems taking performance in consideration.&lt;/p&gt;

    &lt;p&gt;Be confident that you can handle tasks on complex data models.&lt;/p&gt;

    &lt;p&gt;Fix n+1 queries in the best way possible taking into account different angles.&lt;/p&gt;

    &lt;p&gt;Be one of those who help the team... next week&lt;/p&gt;

    &lt;h3&gt;Learn how to fix n+1 queries on rails&lt;/h3&gt;

    &lt;p&gt;Start your road to fix n+1 queries like a pro, with my ebook &lt;strong&gt;Fix n+1 queries on rails&lt;/strong&gt; that will help you:&lt;/p&gt;

    &lt;ul class="list pl3"&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Forget about… “Why active record is ignoring my includes?”&lt;/li&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Learn different ways to solve n+1 queries beyond includes&lt;/li&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Learn different preloading techniques to avoid n+1 queries&lt;/li&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Learn how to count records without n+1 queries&lt;/li&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Learn to use counter caches&lt;/li&gt;
      &lt;li class="mv3"&gt;&lt;i class="fas fa-check red mr2"&gt;&lt;/i&gt; Learn how to cache custom counts and other computed values with concurrency in mind&lt;/li&gt;
    &lt;/ul&gt;

    &lt;div class="tc mt4"&gt;
      &lt;a class="f4 link dim br2 ph4 pv3 mb2 dib white bg-red" href="https://bhserna.gumroad.com/l/fix-n-1-queries-on-rails"&gt;Buy the ebook&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class="bg-white pv5 ph3"&gt;
  &lt;div class="measure center lh-copy"&gt;
    &lt;h2 id="toc"&gt;Table of contents&lt;/h2&gt;

    &lt;p&gt;Here is the full table of contents of the book.&lt;/p&gt;

    &lt;h3 id="free_sample"&gt;No more… “Why active record is ignoring my includes?”&lt;/h3&gt;

    &lt;ol&gt;
      &lt;li&gt;Explain what is an n+1 queries problem&lt;/li&gt;
      &lt;li&gt;Identify when ActiveRecord will execute a query&lt;/li&gt;
      &lt;li&gt;Solve the latest comment example&lt;/li&gt;
      &lt;li&gt;Detect n+1 queries by watching the logs&lt;/li&gt;
      &lt;li&gt;Tools to detect n+1 queries&lt;/li&gt;
    &lt;/ol&gt;

    &lt;h3&gt;Use preloading to avoid n+1 queries&lt;/h3&gt;

    &lt;ol&gt;
      &lt;li&gt;Joins does not preload&lt;/li&gt;
      &lt;li&gt;Preload direct associations&lt;/li&gt;
      &lt;li&gt;Preload nested associations&lt;/li&gt;
      &lt;li&gt;Difference between preload, eager load or includes&lt;/li&gt;
      &lt;li&gt;I prefer to use preload by default&lt;/li&gt;
      &lt;li&gt;Preload scopes with scoped associations&lt;/li&gt;
      &lt;li&gt;Simplify preloading with has many through associations&lt;/li&gt;
      &lt;li&gt;Use a “preload object” for complex preloading&lt;/li&gt;
      &lt;li&gt;Sometimes preloading can hurt the performance&lt;/li&gt;
    &lt;/ol&gt;

    &lt;h3&gt;Count without n+1 queries&lt;/h3&gt;

    &lt;ol&gt;
      &lt;li&gt;What is the difference between count, size and length&lt;/li&gt;
      &lt;li&gt;Common mistakes with counts&lt;/li&gt;
      &lt;li&gt;How to preload counts in a list&lt;/li&gt;
    &lt;/ol&gt;

    &lt;h3&gt;Save counts with counter caches&lt;/h3&gt;

    &lt;ol&gt;
      &lt;li&gt;Add counter cache to a new association&lt;/li&gt;
      &lt;li&gt;Custom counter cache columns names&lt;/li&gt;
      &lt;li&gt;Reset the counters for and existent association with just a few records&lt;/li&gt;
      &lt;li&gt;Reset the counters with SQL, for associations with many records&lt;/li&gt;
      &lt;li&gt;Using counter caches in your views&lt;/li&gt;
    &lt;/ol&gt;

    &lt;h3&gt;Caching custom counts and other computed values&lt;/h3&gt;

    &lt;ol&gt;
      &lt;li&gt;Fix n+1 queries by caching computed values&lt;/li&gt;
      &lt;li&gt;Be aware that you can save incorrect values due to race conditions&lt;/li&gt;
      &lt;li&gt;Tips to avoid race conditions saving computed values&lt;/li&gt;
    &lt;/ol&gt;

    &lt;div class="mt4 tc"&gt;
      &lt;a class="f4 link dim br2 ph4 pv3 mb2 dib white bg-red" href="https://bhserna.gumroad.com/l/fix-n-1-queries-on-rails"&gt;Buy the ebook&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

</content>
  </entry>
  <entry>
    <title>"Please help me with my n+1 problem..."</title>
    <link rel="alternate" href="https://bhserna.com/n-plus-1-queries-basics"/>
    <id>https://bhserna.com/n-plus-1-queries-basics</id>
    <published>2021-05-14T00:28:00Z</published>
    <updated>2021-05-14T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;You are feelling bad for asking it again&amp;hellip; &amp;ldquo;Why active record is ignoring my
&lt;code&gt;includes&lt;/code&gt;?&amp;rdquo;.&lt;/p&gt;

&lt;h3&gt;&amp;ldquo;Is my fix right?&amp;rdquo;&lt;/h3&gt;

&lt;p&gt;Finding &amp;ldquo;a fix&amp;rdquo; can make you feel like pro, but&amp;hellip; Does it really solves the
problem? Is it elegant enough?&lt;/p&gt;

&lt;p&gt;You know that &lt;code&gt;includes&lt;/code&gt; can sometimes fix the problem&amp;hellip; You are just not sure
why sometimes it doesn&amp;rsquo;t.&lt;/p&gt;

&lt;h3&gt;&amp;ldquo;I know how to fix the problem!&amp;rdquo;&lt;/h3&gt;

&lt;p&gt;But what if you could? What if you knew exactly how to solve your n+1 queries problems?
What if you could be the one that help others in your team?&amp;hellip; Next week.&lt;/p&gt;

&lt;p&gt;It&amp;rsquo;s true n+1 queries can be a pain, and they will appear&amp;hellip; but they don&amp;rsquo;t
need to appear on production. &lt;/p&gt;

&lt;h3&gt;Undestand the basics to avoid n+1 queries&lt;/h3&gt;

&lt;p&gt;Start your road to avoid n+1 queries like a pro, with &lt;strong&gt;my free ebook &amp;ldquo;N+1 queries basics&amp;rdquo;&lt;/strong&gt;&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand &lt;code&gt;preload&lt;/code&gt;, &lt;code&gt;eager_load&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt; and &lt;code&gt;joins&lt;/code&gt; and the difference between them.&lt;/li&gt;
&lt;li&gt;Learn to identify when active record will execute your query.&lt;/li&gt;
&lt;li&gt;Learn the tools that can help you detect n+1 queries before they hit production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Start solving n+1 queries problems with total confidence!&lt;/h3&gt;

&lt;p&gt;You’ll learn just enough fundamentals to be fluent preloading associations with
ActiveRecord, and start helping your team to avoid n+1 queries on production.&lt;/p&gt;

&lt;p&gt;Start shipping efficient code, &lt;strong&gt;sign up and download my free ebook&lt;/strong&gt; and
you&amp;rsquo;ll be enjoying the power of Rails again, in no time.&lt;/p&gt;

&lt;div class="mt4 mh0-ns"&gt;
  &lt;script async data-uid="389b58b58f" src="https://bhserna.ck.page/389b58b58f/index.js"&gt;&lt;/script&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>MdRecord: File-backed models for Rails</title>
    <link rel="alternate" href="https://bhserna.com/md-record-file-backed-models-for-rails"/>
    <id>https://bhserna.com/md-record-file-backed-models-for-rails</id>
    <published>2025-12-29T12:00:00Z</published>
    <updated>2025-12-29T12:00:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I just released &lt;a href="https://github.com/bhserna/md_record"&gt;md_record&lt;/a&gt;, a Ruby gem that provides an ActiveRecord-like interface for markdown files with YAML frontmatter.&lt;/p&gt;

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

&lt;h2&gt;Why?&lt;/h2&gt;

&lt;p&gt;I needed a simple way to manage content like blog posts and guides in my Rails apps without a database. I wanted something that felt like ActiveRecord but worked with markdown files.&lt;/p&gt;

&lt;h2&gt;Installation&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"md_record"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Basic Usage&lt;/h2&gt;

&lt;p&gt;Create a model that inherits from &lt;code&gt;MdRecord::Base&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;MdRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By default, files are loaded from &lt;code&gt;app/views/posts/&lt;/code&gt; (based on the model name).&lt;/p&gt;

&lt;p&gt;Create markdown files with YAML frontmatter:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight markdown"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My First Post&lt;/span&gt;
&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A short description&lt;/span&gt;
&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="s"&gt;published_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-12-29&lt;/span&gt;
&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Content here&lt;/span&gt;

Your markdown content...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then use it like ActiveRecord:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;        &lt;span class="c1"&gt;# All posts&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;published&lt;/span&gt;  &lt;span class="c1"&gt;# Only published posts&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Find by slug or raise MdRecord::RecordNotFound&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Custom Path&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MdRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;md_record_path&lt;/span&gt; &lt;span class="s2"&gt;"content/articles"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Categorized Content&lt;/h2&gt;

&lt;p&gt;For content organized in categories (like documentation or guides):&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Guide&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;MdRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;MdRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Categorizable&lt;/span&gt;

  &lt;span class="n"&gt;md_record_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"getting-started"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Getting Started"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;position: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"advanced"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Advanced"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;position: &lt;/span&gt;&lt;span class="mi"&gt;2&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;p&gt;Files are organized in subfolders:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;app/views/guides/
  getting-started/
    first-steps.md
  advanced/
    custom-config.md
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Guide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;categories&lt;/span&gt;  &lt;span class="c1"&gt;# Returns array of MdRecord::Category objects&lt;/span&gt;
&lt;span class="n"&gt;guide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"getting-started"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Check if guide belongs to category&lt;/span&gt;
&lt;span class="n"&gt;guide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category_name&lt;/span&gt;  &lt;span class="c1"&gt;# "Getting Started"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Rails Integration&lt;/h2&gt;

&lt;p&gt;The gem includes a Railtie that automatically maps &lt;code&gt;MdRecord::RecordNotFound&lt;/code&gt; to a 404 response, just like &lt;code&gt;ActiveRecord::RecordNotFound&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"non-existent"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# raises MdRecord::RecordNotFound&lt;/span&gt;
&lt;span class="c1"&gt;# Rails returns 404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Rendering to HTML&lt;/h2&gt;

&lt;p&gt;Each record can render its markdown content to HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my-post"&lt;/span&gt;&lt;span class="p"&gt;)&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;to_html&lt;/span&gt;  &lt;span class="c1"&gt;# Returns HTML string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Links&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/md_record"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rubygems.org/gems/md_record"&gt;RubyGems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Fixing a bug in my lateral joins queries with rails</title>
    <link rel="alternate" href="https://bhserna.com/fixing-a-bug-in-my-lateral-joins-queries-with-rails"/>
    <id>https://bhserna.com/fixing-a-bug-in-my-lateral-joins-queries-with-rails</id>
    <published>2025-01-21T12:00:00Z</published>
    <updated>2025-01-21T12:00:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I have some &lt;a href="https://bhserna.com/fetching-the-top-n-per-group-with-a-lateral-join-with-rails"&gt;posts&lt;/a&gt; &lt;a href="https://bhserna.com/5-ways-to-fetch-the-latest-n-of-each-record-on-rails"&gt;about&lt;/a&gt; using lateral joins in rails to fetch the &amp;ldquo;top N&amp;rdquo; of each record. Some months ago &lt;a href="https://x.com/bensheldon"&gt;Ben Sheldon&lt;/a&gt; &lt;a href="https://github.com/bhserna/last_n_per_user_lateral_join/pull/1"&gt;helped me see a performance problem&lt;/a&gt; on the queries that I was using.&lt;/p&gt;

&lt;p&gt;Here I try to explain the problem, the solution proposed by him, and also an introduction about a &lt;a href="https://github.com/bensheldon/activerecord-has_some_of_many"&gt;tool he published to build this kind of &amp;ldquo;top N&amp;rdquo; queries using lateral joins&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;The problem&lt;/h2&gt;

&lt;p&gt;In his words, &amp;ldquo;The outermost condition of the association (e.g. &lt;code&gt;WHERE user_id IN ($1, ...)&lt;/code&gt;) doesn&amp;rsquo;t get pushed down in the current query. This leads to the lateral join being applied to all records before the condition is applied.&amp;rdquo;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EXPLAIN&lt;/code&gt; helps to see this problem. This is the example that has the problem:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      JOIN LATERAL (
        SELECT * FROM posts
        WHERE user_id = users.id
        ORDER BY id DESC LIMIT :limit
      ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"users count: 100"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"posts count: 10000"&lt;/span&gt;
&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This returns&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;users count: 100
posts count: 10000
EXPLAIN SELECT "posts".* FROM (SELECT selected_posts.* FROM "users" JOIN LATERAL (
          SELECT * FROM posts
          WHERE user_id = users.id
          ORDER BY id DESC LIMIT 3
        ) AS selected_posts ON TRUE) posts WHERE "posts"."user_id" IN ($1, $2) [["user_id", 1], ["user_id", 2]]
                                               QUERY PLAN
--------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.29..1139.25 rows=100 width=25)
   -&amp;gt;  Seq Scan on users  (cost=0.00..2.00 rows=100 width=8)
   -&amp;gt;  Subquery Scan on selected_posts  (cost=0.29..11.36 rows=1 width=25)
         Filter: (selected_posts.user_id = ANY ('{1,2}'::integer[]))
         -&amp;gt;  Limit  (cost=0.29..11.32 rows=3 width=25)
               -&amp;gt;  Index Scan Backward using posts_pkey on posts  (cost=0.29..368.29 rows=100 width=25)
                     Filter: (user_id = users.id)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this example I have 100 users in the database, and even when I used &lt;code&gt;limit(2)&lt;/code&gt; in the query, the plan shows 100 rows, instead of 2.&lt;/p&gt;

&lt;h2&gt;Proposed solution&lt;/h2&gt;

&lt;p&gt;The proposed solution by &lt;a href="https://x.com/bensheldon"&gt;Ben Sheldon&lt;/a&gt;, looks like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;selected_posts_table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Arel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Table&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="s1"&gt;'selected_posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      JOIN LATERAL (
        SELECT * FROM posts
        WHERE user_id = users.id
        ORDER BY id DESC LIMIT :limit
      ) AS &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;selected_posts_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arel_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;excluding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;selected_posts_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ANALYZE users, posts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"users count: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"posts count: &lt;/span&gt;&lt;span class="si"&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;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here using Arel, he &lt;code&gt;SELECT users.id as user_id&lt;/code&gt; and then enumerates the columns from the Post record excluding the &lt;code&gt;user_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In his words, he does it because&amp;hellip; &amp;ldquo;The error happens because the query in the sub-select has two &lt;code&gt;user_id&lt;/code&gt; columns: one from &lt;code&gt;users.id AS user_id&lt;/code&gt; and then the &lt;code&gt;user_id&lt;/code&gt; from &lt;code&gt;latest.*&lt;/code&gt;. That&amp;rsquo;s why it&amp;rsquo;s necessary to enumerate the columns to omit the latter. It&amp;rsquo;s not great looking 😞&amp;rdquo;&lt;/p&gt;

&lt;p&gt;And now the &lt;code&gt;EXPLAIN&lt;/code&gt; shows that the scan for users is limited to 2 users.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;users count: 100
posts count: 10000
EXPLAIN SELECT "posts".* FROM (SELECT "users"."id" AS user_id, "selected_posts"."id", "selected_posts"."title" FROM "users" JOIN LATERAL (
          SELECT * FROM posts
          WHERE user_id = users.id
          ORDER BY id DESC LIMIT 3
        ) AS selected_posts ON TRUE) posts WHERE "posts"."user_id" IN ($1, $2) [["user_id", 1], ["user_id", 2]]
                                            QUERY PLAN
--------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.29..25.02 rows=6 width=29)
   -&amp;gt;  Seq Scan on users  (cost=0.00..2.25 rows=2 width=8)
         Filter: (id = ANY ('{1,2}'::bigint[]))
   -&amp;gt;  Limit  (cost=0.29..11.32 rows=3 width=25)
         -&amp;gt;  Index Scan Backward using posts_pkey on posts  (cost=0.29..368.29 rows=100 width=25)
               Filter: (user_id = users.id)
(6 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;A new tool to define top-N-per-group associations using lateral joins&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://x.com/bensheldon"&gt;Ben Sheldon&lt;/a&gt; published a new tool called &lt;a href="https://github.com/bensheldon/activerecord-has_some_of_many"&gt;activerecord-has_some_of_many&lt;/a&gt; that will give you methods like &lt;code&gt;has_one_of_many&lt;/code&gt; and &lt;code&gt;has_some_of_many&lt;/code&gt; to automatically build associations like the one in this example.&lt;/p&gt;

&lt;p&gt;It will help you build &amp;ldquo;top N&amp;rdquo; queries using lateral joins that you will be able to preload (or &lt;code&gt;includes&lt;/code&gt;) to avoid N+1 queries and also are compatible with typical queries and batch methods (&lt;code&gt;find_each&lt;/code&gt;, &lt;code&gt;in_batches&lt;/code&gt;, &lt;code&gt;find_in_batches&lt;/code&gt;). &lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Simple Searchable module for searching with Rails and SQLite's LIKE</title>
    <link rel="alternate" href="https://bhserna.com/simple-searchable-module-for-searching-with-rails-and-sqlite-like"/>
    <id>https://bhserna.com/simple-searchable-module-for-searching-with-rails-and-sqlite-like</id>
    <published>2024-02-09T23:33:00Z</published>
    <updated>2024-02-09T23:33:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Yesterday, I found myself &lt;a href="https://twitter.com/bhserna/status/1755371440633774261"&gt;struggling&lt;/a&gt; with &lt;a href="https://github.com/oldmoe/litestack/wiki/Litesearch-ActiveRecord-guide"&gt;&amp;ldquo;litesearch&amp;rdquo;&lt;/a&gt;, and in my frustration, I decided to create a simple &amp;ldquo;Searchable&amp;rdquo; module to perform a &amp;ldquo;LIKE&amp;rdquo; search but with a cleaner interface/API.&lt;/p&gt;

&lt;p&gt;If you&amp;rsquo;re looking for a straightforward way to implement a search feature in your application using &amp;ldquo;LIKE&amp;rdquo;, this post might be helpful to you.&lt;/p&gt;

&lt;p&gt;&amp;hellip;However, it turns out &lt;strong&gt;the issue wasn&amp;rsquo;t with &amp;ldquo;litesearch&amp;rdquo; at all&lt;/strong&gt;. &lt;a href="https://twitter.com/bhserna/status/1755749698017648649"&gt;It was actually related to the parallelization of the specs&lt;/a&gt;. For now, I&amp;rsquo;ll continue using &amp;ldquo;litesearch&amp;rdquo; instead of the code provided in this post. Nevertheless, I wanted to share it because it might be useful to you (or even to myself in the future).&lt;/p&gt;

&lt;h2&gt;The Searchable Module&lt;/h2&gt;

&lt;p&gt;You can include the following code in &lt;code&gt;app/models/concerns/searchable.rb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Searchable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;cattr_accessor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;searchable_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:searchable_associations_fields&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;searchable_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;searchable_associations_fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;searchable_fields&lt;/span&gt;
      &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable_associations_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;searchable_associations_fields&lt;/span&gt;

      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;left_outer_joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchable_joins&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="n"&gt;searchable_where_clause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="s2"&gt;"%&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;maybe_search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;searchable_joins&lt;/span&gt;
      &lt;span class="n"&gt;searchable_associations_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;searchable_where_clause&lt;/span&gt;
      &lt;span class="n"&gt;all_normalized_searchable_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; LIKE :query"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" OR "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;all_normalized_searchable_fields&lt;/span&gt;
      &lt;span class="n"&gt;normalized_searchable_fields&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;normalized_searchable_associations_fields&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalized_searchable_fields&lt;/span&gt;
      &lt;span class="n"&gt;searchable_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalized_searchable_associations_fields&lt;/span&gt;
      &lt;span class="n"&gt;searchable_associations_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;association&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reflect_on_association&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;association&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table_name&lt;/span&gt;
        &lt;span class="no"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&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;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;How to Use It&lt;/h2&gt;

&lt;p&gt;You can use the module in your models like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Contact&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;

  &lt;span class="n"&gt;search_on&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:full_name&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;:current_company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can also search on associations using a hash key with the name of the association and a list of attributes to search on.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuoteRequest&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;requester&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Contact"&lt;/span&gt;

  &lt;span class="n"&gt;search_on&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;custom_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:brand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:more_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;requester: :full_name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this example, you&amp;rsquo;ll also search on the &lt;code&gt;requester.full_name&lt;/code&gt; attribute, which corresponds to the &lt;code&gt;&amp;quot;contacts.full_name&amp;quot;&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;If you want to search on multiple fields of the &amp;ldquo;requester&amp;rdquo;, you can do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuoteRequest&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="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Searchable&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;requester&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Contact"&lt;/span&gt;

  &lt;span class="n"&gt;search_on&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;custom_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:brand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:more_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;requester: &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;:full_name&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;h2&gt;The tests to show how each part works&lt;/h2&gt;

&lt;p&gt;I didn&amp;rsquo;t write a lot of tests for it, and I didn&amp;rsquo;t write them in a generic way. However, I will show them to you because they can help you understand the purpose of each part of the code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"test_helper"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchableTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt;
  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests metadata"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Keeps the list of fields to search on the record&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:custom_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:brand_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:product_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:more_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:quantity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Keeps a hash with the list of fields to search for each association&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable_associations_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;requester: :full_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Builds the list of fields with the table_name, to be used later in the query&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalized_searchable_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"quote_requests.custom_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests.brand_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests.product_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests.more_info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests.quantity"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Builds the list of fields from the associations with the table_name, to be used later in the query&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalized_searchable_associations_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"contacts.full_name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# The string to be used in the where clause&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable_where_clause&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests.custom_id LIKE :query OR quote_requests.brand_name LIKE :query OR quote_requests.product_name LIKE :query OR quote_requests.more_info LIKE :query OR quote_requests.quantity LIKE :query OR contacts.full_name LIKE :query"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"contacts example"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pedro"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Perez"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;"quote_requests example"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pedro"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;quote_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:one&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;QuoteRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Perez"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="n"&gt;quote_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:two&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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;h2&gt;Maybe  you can use it&lt;/h2&gt;

&lt;p&gt;While I won&amp;rsquo;t be using this module for now due to my resolution of the issue with &amp;ldquo;litesearch&amp;rdquo;, perhaps you may find it useful.&lt;/p&gt;

&lt;p&gt;Whether you&amp;rsquo;re interested in adapting it for PostgreSQL or exploring alternative search solutions, I hope this code may serve as a helpful reference or source of inspiration.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A "read-more" behavior truncating by number of lines with css line-clamp and Stimulus.js</title>
    <link rel="alternate" href="https://bhserna.com/read-more-line-clamp-stimulus"/>
    <id>https://bhserna.com/read-more-line-clamp-stimulus</id>
    <published>2021-04-03T00:28:00Z</published>
    <updated>2021-04-03T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I have already shared &lt;a href="truncating-multiple-line-text-read-more.html"&gt;a way of implementing a &amp;ldquo;read-more&amp;rdquo; behavior truncating by the number of lines instead of the number of words&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But now I want to share how you can do it using the &lt;code&gt;line-clamp&lt;/code&gt; css property.&lt;/p&gt;

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

&lt;h2 id="example"&gt;The example&lt;/h2&gt;

&lt;iframe class="w-100 ba b--light-blue" style="height: 300px" src="read-more-line-clamp-stimulus/example.html"&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href="read-more-line-clamp-stimulus/example.html"&gt;Visit example page&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="html_and_css"&gt;The html and css&lt;/h2&gt;

&lt;p&gt;Your are going to need&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;linesValue&lt;/code&gt; to configure the number of lines that we want to display,&lt;/li&gt;
&lt;li&gt;A class to &lt;code&gt;hide&lt;/code&gt; the buttons and a class to &lt;code&gt;truncate&lt;/code&gt; the content&amp;hellip;&lt;/li&gt;
&lt;li&gt;3 targets, the &lt;code&gt;content&lt;/code&gt;, the &lt;code&gt;moreButton&lt;/code&gt; and the &lt;code&gt;lessButton&lt;/code&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;3 actions

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resize@window-&amp;gt;read-more#render&lt;/code&gt; - to calculate the truncation on rezise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showMore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showLess&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.hide&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.line-clamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&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;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"read-more"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-lines-value=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-hide-class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-truncate-class=&lt;/span&gt;&lt;span class="s"&gt;"line-clamp"&lt;/span&gt;
     &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"resize@window-&amp;gt;read-more#render"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    The content that you want to truncate...
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"moreButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showMore"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show more
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"lessButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showLess"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show less
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="javascript"&gt;The javascript&lt;/h2&gt;

&lt;p&gt;You are going to use &lt;code&gt;line-clamp&lt;/code&gt; to truncate the content, assigning the configured &lt;code&gt;linesValue&lt;/code&gt; to the property &lt;code&gt;-webkit-line-clamp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But you are going to do it just if the &lt;code&gt;height&lt;/code&gt; is greater that the &lt;code&gt;expectedHeigt&lt;/code&gt; that is the product of the &lt;code&gt;linesValue&lt;/code&gt; with the &lt;code&gt;lineHeight&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"@hotwired/stimulus"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"moreButton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lessButton"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"truncate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hide"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&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="nx"&gt;showMore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;truncateContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-webkit-line-clamp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linesValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linesValue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&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;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Other ways to do it&amp;hellip;&lt;/h2&gt;

&lt;p&gt;If for some reason, you can&amp;rsquo;t use &lt;code&gt;line-clamp&lt;/code&gt;, maybe you can try doing the &lt;a href="truncating-multiple-line-text-read-more.html"&gt;truncation with custom javascript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you can truncate by number of characters, you can check &lt;a href="read-more-rails-truncate-stimulus.html"&gt;this example using the &amp;ldquo;truncate&amp;rdquo; helper.&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Copy shoes.rb and use stacks and flows to build layouts</title>
    <link rel="alternate" href="https://bhserna.com/copy-shoes-rb-and-use-stacks-and-flows-to-build-layouts"/>
    <id>https://bhserna.com/copy-shoes-rb-and-use-stacks-and-flows-to-build-layouts</id>
    <published>2024-01-24T00:07:00Z</published>
    <updated>2024-01-24T00:07:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I was reading about &lt;a href="http://shoesrb.com/"&gt;shoes.rb&lt;/a&gt; and, while scanning the book and walkthrough, I discovered a pattern that caught my attention for its power and simplicity.&lt;/p&gt;

&lt;p&gt;The combination of &amp;ldquo;stacks&amp;rdquo; and &amp;ldquo;flows&amp;rdquo; allows you to build complex layouts, like the following example in Ruby:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Shoes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="s2"&gt;"#EFC"&lt;/span&gt;
  &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#BE8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strokewidth: &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;margin: &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="s2"&gt;"Enter your name"&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;edit_line&lt;/span&gt;
      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;What are a stacks and a flows?&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;stack&lt;/strong&gt; places one thing on top of another, similar to blocks in HTML.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;flow&lt;/strong&gt; arranges items next to each other on a horizontal line, like inline blocks in HTML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With CSS, you can implement them like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nc"&gt;.stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.flow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wrap&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;p&gt;Here&amp;rsquo;s a pen with some examples from the walkthrough to help you visualize how you can combine these two CSS rules:&lt;/p&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary&gt;View CodePen Example&lt;/summary&gt;
  &lt;p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="NWJbvrW" data-user="bhserna" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"&gt;
  &lt;span&gt;See the Pen &lt;a href="https://codepen.io/bhserna/pen/NWJbvrW"&gt;
  Untitled&lt;/a&gt; by Benito Serna (&lt;a href="https://codepen.io/bhserna"&gt;@bhserna&lt;/a&gt;)
  on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
  &lt;/p&gt;
  &lt;script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"&gt;&lt;/script&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Personal testing of the idea&lt;/h2&gt;

&lt;p&gt;I have been using a slightly modified version of these CSS classes in a personal project where I am also using &lt;a href="https://open-props.style/"&gt;open-props&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These are the CSS classes that I am using:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nc"&gt;.stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="err"&gt;&amp;amp;.sm&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.lg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.flow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="err"&gt;&amp;amp;.centered&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.justify-between&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.sm&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;.lg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I don&amp;rsquo;t have enough experience with them to be sure that you should adopt these classes in all your projects, but I think you should at least test them in your personal projects and play around with them.&lt;/p&gt;

&lt;p&gt;If you use something like Tailwind CSS, maybe you can write a partial or component to introduce this abstraction.&lt;/p&gt;

&lt;h2&gt;Maybe this names can break your UI kit&lt;/h2&gt;

&lt;p&gt;The names &amp;ldquo;stack&amp;rdquo; and &amp;ldquo;flow&amp;rdquo; are used as UI elements by some HTML or native UI kits.&lt;/p&gt;

&lt;p&gt;So, if you are testing them, check if these classes are not already used in your project.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fix n+1 queries by caching computed values</title>
    <link rel="alternate" href="https://bhserna.com/fix-n+1-queries-by-caching-computed-values"/>
    <id>https://bhserna.com/fix-n+1-queries-by-caching-computed-values</id>
    <published>2023-07-19T23:19:00Z</published>
    <updated>2023-07-19T23:19:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;N+1 queries are not always a problem, but I have seen that most of the n+1 queries that are really a problem are when we need to fetch data to compute something.&lt;/p&gt;

&lt;p&gt;Here I will try to share some examples of posible expensive computations candidates to be cached and some patterns that you could use to save different kind of values.&lt;/p&gt;

&lt;h2&gt;Some examples of possible expensive computations&lt;/h2&gt;

&lt;p&gt;I think the most common computation in many apps will be a count. It is that common that rails already have &amp;ldquo;counter caches&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;But sometimes you will need to save counts where a counter cache won&amp;rsquo;t be enough, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Counts for &amp;ldquo;has many through&amp;rdquo; associations.&lt;/li&gt;
&lt;li&gt;Counts for a scope of the association, like just the &amp;ldquo;positive reactions&amp;rdquo;, or the &amp;ldquo;completed orders&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or maybe other examples where you need other type of aggregation like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The balance in an account&lt;/li&gt;
&lt;li&gt;The a profile completeness percentage&lt;/li&gt;
&lt;li&gt;A TIR, current value of a portafolio, or other financial calculations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Should you always cache computations?&lt;/h2&gt;

&lt;p&gt;No, not really, you should evaluate the pros and cons in each situation.&lt;/p&gt;

&lt;p&gt;Sometimes you will need a way to cache the value since the first implementation, but other times it will be better to wait and learn a little more about the problem and cache the value until is really necessary.&lt;/p&gt;

&lt;p&gt;Sometimes pre-compute the value will be very easy, but sometimes could be more expensive or very hard, or maybe just not needed.&lt;/p&gt;

&lt;h2&gt;Patterns to save computed values&lt;/h2&gt;

&lt;p&gt;There are many ways to save a value, but here I will show you three patterns that I use a lot.&lt;/p&gt;

&lt;h3&gt;Using a before_save callback&lt;/h3&gt;

&lt;p&gt;You could use it for calculations that involve information on the same record, like a profile completeness calculation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&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;before_save&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set_completeness_percentage&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_completeness_percentage&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completeness_percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_completeness_percentage&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;p&gt;When you use a &lt;code&gt;before_save&lt;/code&gt; callback, you don&amp;rsquo;t need to &lt;a href="https://guides.rubyonrails.org/active_record_callbacks.html#available-callbacks"&gt;save the record&lt;/a&gt;, you just need to change the value and the record will be saved including the fields you modified.&lt;/p&gt;

&lt;h3&gt;Using after_touch callback&lt;/h3&gt;

&lt;p&gt;You could use it when you need to save or change a value for things that happen in other records, like updating the balance when an entry is created or updated:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="n"&gt;after_touch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_balance&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Using after_commit + background job&lt;/h2&gt;

&lt;p&gt;Use it for calculations are expensive to run on the same process and is better to run them asynchronously.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&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;after_commit&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set_completeness_percentage_later&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_completeness_percentage_later&lt;/span&gt;
    &lt;span class="no"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetCompletenessPercentageProfileJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_completeness_percentage&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completeness_percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_completeness_percentage&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SetCompletenessPercentageProfileJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_completeness_percentage&lt;/span&gt;
    &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Be aware of race conditions&lt;/h2&gt;

&lt;p&gt;When saving computed values in the database in your rails app, you must be aware that is possible to find unexpected errors in the result thanks to race conditions.&lt;/p&gt;

&lt;p&gt;I have other article that can help you visualize how race conditions can make you save incorrect values even when your calculation is correct.&lt;/p&gt;

&lt;p&gt;You can read it on: &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;Saving incorrect computed values thanks to race conditions&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tools to help you detect n+1 queries</title>
    <link rel="alternate" href="https://bhserna.com/tools-to-help-you-detect-n-1-queries"/>
    <id>https://bhserna.com/tools-to-help-you-detect-n-1-queries</id>
    <published>2021-03-01T12:57:00Z</published>
    <updated>2021-03-01T12:57:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;There are many tools that can help you detect n+1 queries in different ways.&lt;/p&gt;

&lt;p&gt;This is a little reference of some of those tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#strict_loading"&gt;Strict loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#rack_mini_profiler"&gt;Rack mini profiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#bullet"&gt;Bullet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#prosopite"&gt;Prosopite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n_plus_one_control"&gt;n plus one control&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don&amp;rsquo;t need to use all of them, but is good to know that they exists and how they can help you.&lt;/p&gt;

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

&lt;h2 id="strict_loading"&gt;Rails strict_loading&lt;/h2&gt;

&lt;h3&gt;How it can help you?&lt;/h3&gt;

&lt;p&gt;You can add &lt;code&gt;#strict_loading!&lt;/code&gt; to any record to prevent lazy loading of
associations. Strict loading will cascade down from the parent record to all the
associations to help you catch any places where you may want to use preload
instead of lazy loading.&lt;/p&gt;

&lt;p&gt;On a record:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_loading!&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&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;StrictLoadingViolationError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On a relation:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_loading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&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;StrictLoadingViolationError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On an association definition:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;strict_loading: &lt;/span&gt;&lt;span class="kp"&gt;true&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&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;StrictLoadingViolationError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Per model configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_loading_by_default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&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;StrictLoadingViolationError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to enable strict loading by default you can do it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict_loading_by_default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you can change the action on strict loading violation from &lt;code&gt;:raise&lt;/code&gt; (default) to &lt;code&gt;:log&lt;/code&gt; you can do it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_on_strict_loading_violation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Core.html#method-i-strict_loading-21"&gt;ActiveRecord::Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-strict_loading"&gt;ActiveRecord::QueryMethods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rails/rails/pull/37400"&gt;Pull request: Add &lt;code&gt;strict_loading&lt;/code&gt; mode to optionally prevent lazy loading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="rack_mini_profiler"&gt;Rack mini profiler&lt;/h2&gt;

&lt;h3&gt;How it can help you?&lt;/h3&gt;

&lt;p&gt;It can help you with more than just detecting n+1 queries. It is a production and development profiler, it allows you to quickly isolate performance bottlenecks, both on the server and client.&lt;/p&gt;

&lt;p&gt;It can help you with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database profiling&lt;/li&gt;
&lt;li&gt;Call-stack profiling&lt;/li&gt;
&lt;li&gt;Memory profiling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;How does the report looks like?&lt;/h3&gt;

&lt;p&gt;It displays a speed badge for every html page that if you click it, it will show you a page with the profiling information of the current page.&lt;/p&gt;

&lt;p&gt;&lt;img alt="miniprofiler" src="/2021-03-01-toolt-to-help-you-detect-n-1-queries/miniprofiler.png" /&gt;&lt;/p&gt;

&lt;p&gt;And if you click on of the sql queries count, it will show you a list with all the queries&amp;hellip;&lt;/p&gt;

&lt;p&gt;&lt;img alt="miniprofiler" src="/2021-03-01-toolt-to-help-you-detect-n-1-queries/miniprofiler-queries.png" /&gt;&lt;/p&gt;

&lt;p&gt;Although it will not tell you exactly that you have an n+1 quieries problem, it can help you a lot to visualize it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MiniProfiler/rack-mini-profiler"&gt;github/MiniProfiler/rack-mini-profiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://samsaffron.com/archive/2012/07/12/miniprofiler-ruby-edition"&gt;The announcement posts from 2012&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="bullet"&gt;Bullet&lt;/h2&gt;

&lt;h3&gt;How can it help you?&lt;/h3&gt;

&lt;p&gt;It will watch your queries while you develop your application and notify you when it detects one of this problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;n+1 queries&lt;/li&gt;
&lt;li&gt;eager-loaded associations which are not used&lt;/li&gt;
&lt;li&gt;unnecessary COUNT queries which could be avoided with a counter cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use it on the development and testing environments.&lt;/p&gt;

&lt;p&gt;Sometimes Bullet may notify you of query problems you don&amp;rsquo;t care to fix, or which come from outside your code. You can whitelist these to ignore them.&lt;/p&gt;

&lt;h3&gt;How does the error report looks like?&lt;/h3&gt;

&lt;p&gt;For n+1 queries&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;GET /posts
USE eager loading detected
  Post =&amp;gt; [:comments]
  Add to your query: .includes([:comments])
Call stack
  /Users/benitoserna/code/bullet-test/app/views/posts/index.html.erb:20:in `map'
  /Users/benitoserna/code/bullet-test/app/views/posts/index.html.erb:20:in `block in _app_views_posts_index_html_erb__1178069968615334744_70147771830640'
  /Users/benitoserna/code/bullet-test/app/views/posts/index.html.erb:16:in `_app_views_posts_index_html_erb__1178069968615334744_70147771830640'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For unused eager loading&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;GET /posts
AVOID eager loading detected
  Post =&amp;gt; [:comments]
  Remove from your query: .includes([:comments])
Call stack
&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/flyerhzm/bullet"&gt;github/flyerhzm/bullet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="prosopite"&gt;Prosopite&lt;/h2&gt;

&lt;h3&gt;How can it help you?&lt;/h3&gt;

&lt;p&gt;Prosopite is able to auto-detect Rails N+1 queries, but it also can help you
detect some cases where bullet will give you false positives or false
negatives.&lt;/p&gt;

&lt;p&gt;Prosopite monitors all SQL queries using the Active Support instrumentation and
looks for a pattern which is present in all N+1 query cases: More than one
queries have the same call stack and the same query fingerprint.&lt;/p&gt;

&lt;p&gt;You can use it on the development and testing environments.&lt;/p&gt;

&lt;p&gt;Compared to bullet, Prosopite can auto-detect the following extra cases of N+1 queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N+1 queries after record creations (usually in tests)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;FactoryBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:leg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Leg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="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;l&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chair&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Not triggered by ActiveRecord associations&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Leg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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="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;l&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Chair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chair_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;First/last/pluck of collection associations&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Chair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="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;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;legs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;legs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;legs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Changing the ActiveRecord class with #becomes&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Chair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;becomes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ArmChair&lt;/span&gt;&lt;span class="p"&gt;)&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;ac&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;legs&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;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Mongoid models calling ActiveRecord&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Leg&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Design&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Mongoid&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Document&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="nf"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :chair_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="no"&gt;Integer&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="nf"&gt;def&lt;/span&gt; &lt;span class="n"&gt;chair&lt;/span&gt;
    &lt;span class="vi"&gt;@chair&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Chair&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;id: &lt;/span&gt;&lt;span class="n"&gt;chair_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first!&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;Leg&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Design&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chair&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;How does the error report looks like?&lt;/h3&gt;

&lt;p&gt;The report will show you the N+1 queries detected and the call stack.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;N+1 queries detected:
  SELECT `users`.* FROM `users` WHERE `users`.`id` = 20 LIMIT 1
  SELECT `users`.* FROM `users` WHERE `users`.`id` = 21 LIMIT 1
  SELECT `users`.* FROM `users` WHERE `users`.`id` = 22 LIMIT 1
  SELECT `users`.* FROM `users` WHERE `users`.`id` = 23 LIMIT 1
  SELECT `users`.* FROM `users` WHERE `users`.`id` = 24 LIMIT 1
Call stack:
  app/controllers/thank_you_controller.rb:4:in `block in index'
  app/controllers/thank_you_controller.rb:3:in `each'
  app/controllers/thank_you_controller.rb:3:in `index':
  app/controllers/application_controller.rb:8:in `block in &amp;lt;class:ApplicationController&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/charkost/prosopite"&gt;github/charkost/prosopite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="n_plus_one_control"&gt;NPlusOneControl&lt;/h2&gt;

&lt;h3&gt;How can it help you?&lt;/h3&gt;

&lt;p&gt;It gives you rspec and minitest matchers to prevent the n+1 queries problem.&lt;/p&gt;

&lt;p&gt;It evaluates the code under consideration several times with different scale
factors to make sure that the number of DB queries behaves as expected (i.e.
O(1) instead of O(N)).&lt;/p&gt;

&lt;p&gt;So, it&amp;rsquo;s for performance testing and not feature testing.&lt;/p&gt;

&lt;h3&gt;How does the error report looks like?&lt;/h3&gt;

&lt;p&gt;In the default mode it can give you something like this.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Expected to make the same number of queries, but got:
  10 for N=2
  11 for N=3
Unmatched query numbers by tables:
  resources (SELECT): 2 != 3
  permissions (SELECT): 4 != 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in the &amp;ldquo;verbose&amp;rdquo; mode, it can give you something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Expected to make the same number of queries, but got:
  2 for N=2
  3 for N=3
Unmatched query numbers by tables:
  resources (SELECT): 2 != 3
Queries for N=2
   SELECT "resources".* FROM "resources" WHERE "resources"."deleted_at" IS NULL
   ↳ app/controllers/resources_controller.rb:32:in `index'
   ...
Queries for N=3
   ...
&lt;/code&gt;&lt;/pre&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/palkan/n_plus_one_control"&gt;github/palkan/n plus one control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://evilmartians.com/chronicles/squash-n-plus-one-queries-early-with-n-plus-one-control-test-matchers-for-ruby-and-rails"&gt;Squash N+1 queries early with n plus one control test matchers for Ruby and Rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Truncate in the middle with truncate rails helper</title>
    <link rel="alternate" href="https://bhserna.com/truncate-in-the-middle-with-truncate-rails-helper"/>
    <id>https://bhserna.com/truncate-in-the-middle-with-truncate-rails-helper</id>
    <published>2023-06-19T23:08:00Z</published>
    <updated>2023-06-19T23:08:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you want to truncate a filename, but you want to keep showing the extension of the file. Like “A big file name that…awesome.pdf”. How would you do it?&lt;/p&gt;

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

&lt;h2&gt;The omission parameter&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;:omission&lt;/code&gt; string the last characters will be replaced (defaults to “…”) for a total length not exceeding &lt;code&gt;length&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"And they found that many people were sleeping better."&lt;/span&gt;
&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;omission: &lt;/span&gt;&lt;span class="s1"&gt;'... (continued)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "And they f... (continued)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, you can pass as the omssion string, the last characters of your string like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"And they found that many people were sleeping better.pdf"&lt;/span&gt;
&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;omission: &lt;/span&gt;&lt;span class="s2"&gt;"... &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "And they fo... better.pdf"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>A form with two buttons with formation and formmethod</title>
    <link rel="alternate" href="https://bhserna.com/a-form-with-two-buttons-with-formation-and-formmethod"/>
    <id>https://bhserna.com/a-form-with-two-buttons-with-formation-and-formmethod</id>
    <published>2023-06-08T12:17:00Z</published>
    <updated>2023-06-08T12:17:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you are building a custom CMS. Within the form to edit an &lt;code&gt;Article&lt;/code&gt;, you need to have two buttons: a normal &amp;ldquo;Save&amp;rdquo; button and a new &amp;ldquo;Save and publish&amp;rdquo; button. And maybe, additionally, you will need a third button to delete the article.&lt;/p&gt;

&lt;p&gt;To achieve this, you can use the &lt;code&gt;formaction&lt;/code&gt; and &lt;code&gt;formmethod&lt;/code&gt; attributes.&lt;/p&gt;

&lt;h3&gt;Changing the action with &lt;code&gt;formaction&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;To add the &amp;ldquo;Save and publish&amp;rdquo; button, use the &lt;code&gt;formaction&lt;/code&gt; attribute to override the action in the form:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;article&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;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Save"&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Save and publish"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;formaction: &lt;/span&gt;&lt;span class="n"&gt;save_and_publish_article_path&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;p&gt;This sends the form, along with all the field information, to the path you defined in &lt;code&gt;formaction&lt;/code&gt;. To process the information in a new controller action, define a route:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;save_and_publish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on: :member&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, create the new controller action:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_and_publish&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_and_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&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;h3&gt;Changing the method with &lt;code&gt;formmethod&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;To add the &amp;ldquo;Delete&amp;rdquo; button, use the &lt;code&gt;formmethod&lt;/code&gt; attribute to let HTML override the declared method attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;article&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;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Save"&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt; &lt;span class="s2"&gt;"Delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;formmethod: :delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;confirm: &lt;/span&gt;&lt;span class="s2"&gt;"Are you sure?"&lt;/span&gt; &lt;span class="p"&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="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

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

&lt;p&gt;Most browsers don&amp;rsquo;t support overriding form methods declared through &lt;code&gt;formmethod&lt;/code&gt; other than &amp;ldquo;GET&amp;rdquo; and &amp;ldquo;POST&amp;rdquo;. Rails works around this issue by emulating other methods over POST through a combination of &lt;code&gt;formmethod&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;, and &lt;code&gt;name&lt;/code&gt; attributes.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;accept-charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/articles/1"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"patch"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"authenticity_token"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;formmethod=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_method"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt; &lt;span class="na"&gt;data-confirm=&lt;/span&gt;&lt;span class="s"&gt;"Are you sure?"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>What to do when you need a button_to within a form in Rails</title>
    <link rel="alternate" href="https://bhserna.com/what-to-do-when-you-need-a-button_to-within-a-form-in-rails"/>
    <id>https://bhserna.com/what-to-do-when-you-need-a-button_to-within-a-form-in-rails</id>
    <published>2023-06-05T12:12:00Z</published>
    <updated>2023-06-05T12:12:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you have a form to update a record (let&amp;rsquo;s say a product record) and inside the form, you are showing a list of images, and each image needs a button to remove it. You tried to use button_to but it doesn&amp;rsquo;t work because in html you can have form within a form. What do you do?&lt;/p&gt;

&lt;h2&gt;You can use the “form” attribute&lt;/h2&gt;

&lt;p&gt;You can use a &lt;code&gt;button&lt;/code&gt; tag and define its &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#form"&gt;form attribute&lt;/a&gt;. The value should be the &lt;code&gt;id&lt;/code&gt; of a form located anywhere in the document.&lt;/p&gt;

&lt;p&gt;For example, in the next code, for each persited image, we call render a &lt;code&gt;button&lt;/code&gt; with a form attribute &lt;code&gt;“delete_image_#{image.id}”&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-images"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;image&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;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;form: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/figure&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;span class="nt"&gt;&amp;lt;/div&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;p&gt;And later in the page, for each persisted image, we can render a hidden form with id “delete&lt;em&gt;image&lt;/em&gt;#{image.id}” that will match the previous button.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&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;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;product_image_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;method: :delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&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;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;
</content>
  </entry>
  <entry>
    <title>Simple image manager with active storage</title>
    <link rel="alternate" href="https://bhserna.com/simple-image-manager-with-active-storage"/>
    <id>https://bhserna.com/simple-image-manager-with-active-storage</id>
    <published>2023-05-22T23:25:00Z</published>
    <updated>2023-05-22T23:25:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;If you want to add images to a record but you don&amp;rsquo;t want to use a JavaScript plugin or write any custom JavaScript, you can use a regular file field, Active Storage, and vanilla Rails.&lt;/p&gt;

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

&lt;p&gt;If you want to be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attach many images on create&lt;/li&gt;
&lt;li&gt;Keep adding many images on every update&lt;/li&gt;
&lt;li&gt;Display the images in a gallery&lt;/li&gt;
&lt;li&gt;Be able to remove the images one by one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a tutorial to help you accomplish that.&lt;/p&gt;

&lt;h2&gt;Example: A product with many attached images&lt;/h2&gt;

&lt;p&gt;To use as an example for the tutorial, we are going to have a &lt;code&gt;Product&lt;/code&gt; record that has many attached images like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&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_attached&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Add many images on create and every update&lt;/h2&gt;

&lt;p&gt;If you want to add many attachments to a record using just a file field, but you don&amp;rsquo;t want to remove the previous images from the record on every update, you can use a virtual attribute &amp;ldquo;new_images&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;Instead of using the &lt;code&gt;:images&lt;/code&gt; attribute, you can add a virtual &lt;code&gt;:new_images&lt;/code&gt; attribute, using an &lt;code&gt;attr_reader&lt;/code&gt; and a custom writer like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&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="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:new_images&lt;/span&gt;

  &lt;span class="n"&gt;has_many_attached&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new_images&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And use that attribute in your &lt;code&gt;file_field&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:new_images&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:new_images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;multiple: &lt;/span&gt;&lt;span class="kp"&gt;true&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&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;p&gt;Also, update your &amp;ldquo;params method&amp;rdquo; in the controller:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;product_params&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;new_images: &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;p&gt;This way, when you assign new images, your record will attach the received images instead of updating the images field with the new images.&lt;/p&gt;

&lt;h2&gt;Display the images&lt;/h2&gt;

&lt;p&gt;To display the images, you can put an HTML like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-images"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/figure&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;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Where &amp;ldquo;persisted_images&amp;rdquo; is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;persisted_images&lt;/span&gt;
  &lt;span class="n"&gt;images&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:persisted?&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;p&gt;Maybe you don&amp;rsquo;t need this, but I use this HTML inside the form, and I had some troubles when the record was not valid; it was showing images without content, this solved my problem.&lt;/p&gt;

&lt;h2&gt;Remove the images one by one&lt;/h2&gt;

&lt;p&gt;If you are not displaying the images inside the form, maybe you can put a &lt;code&gt;button_to&lt;/code&gt; remove the image near the image.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-images"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;image&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;button_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;product_image_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;method: :delete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/figure&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;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But if you want to put the images inside your form, you can&amp;rsquo;t use a &lt;code&gt;button_to&lt;/code&gt; because you can&amp;rsquo;t have a form inside a form.&lt;/p&gt;

&lt;p&gt;So one thing you can do is to have a button that will trigger a form that is outside the main form, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:new_images&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:new_images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;multiple: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-images"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;image&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;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s2"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;form: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/figure&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;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&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;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;product_image_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;method: :delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&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;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;p&gt;In the form, we are using a &lt;code&gt;button&lt;/code&gt; tag of type &lt;code&gt;submit&lt;/code&gt; that will trigger a form with id &lt;code&gt;&amp;quot;delete_image_#{image.id}&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&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;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;"Remove"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;form: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And we are creating a form for each image specifying that id:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persisted_images&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;image&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;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;product_image_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;method: :delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="s2"&gt;"delete_image_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&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;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;p&gt;Then on the controller that handles the &lt;code&gt;product_image_path&lt;/code&gt;, you can remove the image with &lt;code&gt;purge&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImagesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set_product&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;purge&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_product&lt;/span&gt;
    &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:product_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;I have a sample application with code available at &lt;a href="https://github.com/bhserna/simple_image_managment"&gt;https://github.com/bhserna/simple&lt;em&gt;image&lt;/em&gt;managment&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Add many attachments without deleting previous ones using ActiveStorage</title>
    <link rel="alternate" href="https://bhserna.com/add-many-attachments-without-deleting-previous-ones-using-activestorage"/>
    <id>https://bhserna.com/add-many-attachments-without-deleting-previous-ones-using-activestorage</id>
    <published>2023-05-16T12:09:00Z</published>
    <updated>2023-05-16T12:09:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;If you want to add many attachments to a record using just a file field, but you don&amp;rsquo;t want to remove the previous images from the record on every update, like in the following code:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:images&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;multiple: &lt;/span&gt;&lt;span class="kp"&gt;true&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&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;p&gt;And instead, on every update you want to add the new images to the record, here is a simple workaround…&lt;/p&gt;

&lt;h2&gt;A virtual new_images attribute&lt;/h2&gt;

&lt;p&gt;Instead of using the &lt;code&gt;:images&lt;/code&gt; attribute, you can add a virtual &lt;code&gt;:new_images&lt;/code&gt; attribute, using an &lt;code&gt;attr_reader&lt;/code&gt; and a custom writer like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&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="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:new_images&lt;/span&gt;

  &lt;span class="n"&gt;has_many_attached&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new_images&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And use that attribute in your &lt;code&gt;file_field&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:new_images&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:new_images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;multiple: &lt;/span&gt;&lt;span class="kp"&gt;true&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;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&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;p&gt;Also, update your “params method” in the controller:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;product_params&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;new_images: &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;p&gt;This way, when you assign new images, your record will attach the received images instead of updating the images field with the new images.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Use after_touch to avoid to handle race conditions saving computed values</title>
    <link rel="alternate" href="https://bhserna.com/use-after_touch-to-avoid-to-handle-race-conditions-saving-computed-values"/>
    <id>https://bhserna.com/use-after_touch-to-avoid-to-handle-race-conditions-saving-computed-values</id>
    <published>2023-03-02T23:16:00Z</published>
    <updated>2023-03-02T23:16:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When saving computed values in the database in your rails app, you &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;must be aware that is possible to find unexpected errors in the result thanks to race conditions.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have already &lt;a href="https://bhserna.com/will-it-save-the-wrong-value.html"&gt;shared an exercise&lt;/a&gt; to help you get more sensitivity about when an implementation can save a wrong value thanks to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want to share one tip to help you avoid save the wrong value due race conditions while trying to save a computed value.&lt;/p&gt;

&lt;h2&gt;Using the account balance as an example&lt;/h2&gt;

&lt;p&gt;To talk about something concrete I will use the “account balance” as an example, but you can use this approach for different types of calculations.&lt;/p&gt;

&lt;h2&gt;The account balance example&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine each &lt;strong&gt;account will need to create many entries concurrently,&lt;/strong&gt; maybe on different background jobs or different requests. So if you want to calculate the balance and save it just after an entry is created, you could have problems with race conditions.&lt;/p&gt;

&lt;h2&gt;Tip: Use after_touch to trigger the operation&lt;/h2&gt;

&lt;p&gt;As far as I understand, if you are using Postgres with the default isolation level &lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED"&gt;Read Committed&lt;/a&gt;, you can compute and save the value on an &lt;code&gt;after_touch&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="n"&gt;after_touch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_balance&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Why does it work?&lt;/h2&gt;

&lt;p&gt;Or at least, why do I think that it works…&lt;/p&gt;

&lt;p&gt;Apparently &lt;code&gt;after_touch&lt;/code&gt; works something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is a change in the association.&lt;/li&gt;
&lt;li&gt;Within the same transaction, rails updates the associated record (&lt;code&gt;account&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Then it performs the other operation that you have configured (&lt;code&gt;update_balance&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL uses by default the &lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED"&gt;Read Committed&lt;/a&gt; isolation level.&lt;/p&gt;

&lt;p&gt;In this mode (as far as I understand), if within a transaction there is an &lt;code&gt;UPDATE&lt;/code&gt; of a row and in another concurrent transaction an &lt;code&gt;UPDATE&lt;/code&gt; of the same row is attempted, PostgreSQL will wait for the first transaction to be committed before continuing with the second one.&lt;/p&gt;

&lt;p&gt;This makes the operations we perform on &lt;code&gt;after_touch&lt;/code&gt; to be isolated and avoids the problem of saving a wrong error due to another transaction registering another entry for the same account concurrently.&lt;/p&gt;

&lt;h2&gt;Do you want to test it?&lt;/h2&gt;

&lt;p&gt;You can check the &lt;a href="https://bhserna.com/custom-computed-values-and-race-conditions-examples-rails.html"&gt;examples to explore possible race conditions when caching custom computed values in Rails&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And more specifically you can test this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/03_after_touch.rb"&gt;example&lt;/a&gt; that uses &lt;code&gt;after_touch&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Are we avoiding race conditions?&lt;/h2&gt;

&lt;p&gt;We are not avoiding race conditions, other problems can happen, but we are at least avoiding saving the wrong value due to race conditions.&lt;/p&gt;

&lt;h2&gt;Is this a perfect fix?&lt;/h2&gt;

&lt;p&gt;No, if the calculation of the value that you are trying to save takes a lot of time and your application is really concurrent, it can queue up many transactions and cause other problems.&lt;/p&gt;

&lt;p&gt;Also the reason why it works, can be a little obscure.&lt;/p&gt;

&lt;h2&gt;Do you know other problems with this solution?&lt;/h2&gt;

&lt;p&gt;If you have experience with other problems with this solution, please leave a comment =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Use with_lock to handle race conditions saving computed values</title>
    <link rel="alternate" href="https://bhserna.com/use-with_lock-to-handle-race-conditions-saving-computed-values"/>
    <id>https://bhserna.com/use-with_lock-to-handle-race-conditions-saving-computed-values</id>
    <published>2023-03-01T00:00:00Z</published>
    <updated>2023-03-01T00:00:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When saving computed values in the database in your rails app, you &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;must be aware that is possible to find unexpected errors in the result thanks to race conditions.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have already &lt;a href="https://bhserna.com/will-it-save-the-wrong-value.html"&gt;shared an exercise&lt;/a&gt; to help you get more sensitivity about when an implementation can save a wrong value thanks to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want to share one tip to help you avoid save the wrong value due race conditions while trying to save a computed value.&lt;/p&gt;

&lt;h2&gt;Using the account balance as an example&lt;/h2&gt;

&lt;p&gt;To talk about something concrete I will use the “account balance” as an example, but you can use this approach for different types of calculations.&lt;/p&gt;

&lt;h2&gt;The account balance example&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine each &lt;strong&gt;account will need to create many entries concurrently,&lt;/strong&gt; maybe on different background jobs or different requests. So if you want to calculate the balance and save it just after an entry is created, you could have problems with race conditions.&lt;/p&gt;

&lt;h2&gt;Tip: Use with lock in the full operation&lt;/h2&gt;

&lt;p&gt;Instead of “just” saving the balance and maybe &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;save a wrong value&lt;/a&gt;, you can wrap the full operation in a &lt;code&gt;with_lock&lt;/code&gt; block.&lt;/p&gt;

&lt;p&gt;By calling &lt;code&gt;with_lock&lt;/code&gt; you will start a transaction and acquire a lock for the row in one go. The block is called from within a transaction, and the object is already locked.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# This block is called within a transaction,&lt;/span&gt;
      &lt;span class="c1"&gt;# and account is already locked&lt;/span&gt;
      &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;update_balance&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can learn more of &lt;code&gt;with_lock&lt;/code&gt; and the &lt;code&gt;ActiveRecord::Locking::Pessimistic&lt;/code&gt; module on the &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html"&gt;rails api&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Are we avoiding race conditions?&lt;/h2&gt;

&lt;p&gt;We are not avoiding race conditions, other problems can happen, but we are at least avoiding saving the wrong value due to race conditions.&lt;/p&gt;

&lt;h2&gt;Is this a perfect fix?&lt;/h2&gt;

&lt;p&gt;No, locks can produce other type of problems, like &lt;a href="https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS"&gt;deadlocks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Do you know other problems with this solution?&lt;/h2&gt;

&lt;p&gt;If you have experience with other problems with this solution, please leave a comment =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Pick a safe previous date to avoid race conditions saving computed values</title>
    <link rel="alternate" href="https://bhserna.com/pick-a-safe-previous-date-to-avoid-race-conditions-saving-computed-values"/>
    <id>https://bhserna.com/pick-a-safe-previous-date-to-avoid-race-conditions-saving-computed-values</id>
    <published>2023-02-14T00:39:00Z</published>
    <updated>2023-02-14T00:39:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When saving computed values in the database in your rails app, you &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;must be aware that is possible to find unexpected errors in the result thanks to race conditions.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have already &lt;a href="https://bhserna.com/will-it-save-the-wrong-value.html"&gt;shared an exercise&lt;/a&gt; to help you get more sensitivity about when an implementation can save a wrong value thanks to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want to share one tip you can try to avoid race conditions when saving a computed value.&lt;/p&gt;

&lt;h2&gt;Using the account balance as an example&lt;/h2&gt;

&lt;p&gt;To talk about something concrete I will use the “account balance” as an example, but you can use this approach for different types of calculations.&lt;/p&gt;

&lt;h2&gt;The account balance example&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine each &lt;strong&gt;account will need to create many entries concurrently,&lt;/strong&gt; maybe on different background jobs or different requests. So if you want to calculate the balance and save it just after an entry is created, you could have problems with race conditions.&lt;/p&gt;

&lt;h2&gt;Tip: Pick a safe previous date&lt;/h2&gt;

&lt;p&gt;If your app is really concurrent and you can’t show posible off value, maybe you can pick a “safe previous date” like “yesterday” or “an hour ago” and present the value for that date.&lt;/p&gt;

&lt;p&gt;You can run a daily rake task to save the balance with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;balance_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;entries&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;created_at: &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_safe_balance!&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_balance_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe_balance_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;save!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_safe_balance_later&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;UpdateSafeBalanceJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&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;UpdateSafeBalanceJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_safe_balance!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# on a daily task&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_each&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;:update_safe_balance_later&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then you can use it in your views indicating the time of the calculation:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
  Balance:
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_to_currency&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_balance&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;small&amp;gt;&lt;/span&gt;Updated &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_balance_time&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Are we really avoiding race conditions?&lt;/h2&gt;

&lt;p&gt;As far as I understand, yes! We are avoiding race conditions because we are calculating the balance for a “safe” point in time. What does “safe” means will depend on your use case.&lt;/p&gt;

&lt;h2&gt;Is this a perfect fix?&lt;/h2&gt;

&lt;p&gt;No, because you are not really showing the “current value”, and you will need to make sure that your users understand that this is a calculation for a previous point in time, and that can be confusing.&lt;/p&gt;

&lt;h2&gt;Reference&lt;/h2&gt;

&lt;p&gt;I learned this tip from &lt;a href="https://www.reddit.com/r/rails/comments/ztr1n1/comment/j1f4ig2/?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3"&gt;recycledcoder&lt;/a&gt; on reddit.&lt;/p&gt;

&lt;h2&gt;Do you know other problems with this solution?&lt;/h2&gt;

&lt;p&gt;If you have experience with other problems with this solution, please leave a comment =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Touch + fragment caching to avoid race conditions saving computed values</title>
    <link rel="alternate" href="https://bhserna.com/touch-fragment-caching-to-avoid-race-conditions-saving-computed-values"/>
    <id>https://bhserna.com/touch-fragment-caching-to-avoid-race-conditions-saving-computed-values</id>
    <published>2023-01-31T23:16:00Z</published>
    <updated>2023-01-31T23:16:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When saving computed values in the database in your rails app, you &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;must be aware that is possible to find unexpected errors in the result due to race conditions.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have already &lt;a href="https://bhserna.com/will-it-save-the-wrong-value.html"&gt;shared an exercise&lt;/a&gt; to help you get more sensitivity about when an implementation can save a wrong value due to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want to share one tip you can try to avoid race conditions when saving a computed value.&lt;/p&gt;

&lt;h2&gt;Using the account balance as an example&lt;/h2&gt;

&lt;p&gt;To talk about something concrete I will use the “account balance” as an example, but you can use this approach for different types of calculations.&lt;/p&gt;

&lt;h2&gt;The account balance example&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now imagine that each &lt;strong&gt;account will need to create many entries concurrently,&lt;/strong&gt; maybe on different background jobs or different requests. So if you want to calculate the balance and save it just after an entry is created, you could have problems with race conditions.&lt;/p&gt;

&lt;h2&gt;Tip: Use touch + fragment caching&lt;/h2&gt;

&lt;p&gt;If you will use the calculated value mostly in your views, and you need a way of saving the value&amp;hellip;&lt;/p&gt;

&lt;p&gt;Instead of trying to save the value in the database when an entry is created, you can use touch on the association and try fragment caching.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Balance: &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_to_currency&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&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;p&gt;If you need to use the value in a list you can use &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html#collection-caching"&gt;collection caching&lt;/a&gt;, and rails will read all cache templates at once instead of one by one.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cached: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Are we really avoiding race conditions?&lt;/h2&gt;

&lt;p&gt;With this &amp;ldquo;fix&amp;rdquo; you are still prune to race conditions, but you are delaying the
problem until the value is used.&lt;/p&gt;

&lt;p&gt;In my understanding even if you get into race conditions, it would not be that
problematic, because even if an other entry is created just after the cache
check, when the page is visited again the cache will be refreshed. I can’t see a
moment where the cache can get stuck on a previous value.&lt;/p&gt;

&lt;h2&gt;Is this a perfect fix?&lt;/h2&gt;

&lt;p&gt;No, you can have other type of problems.&lt;/p&gt;

&lt;p&gt;For example, now you have to deal with cache expiration, in the example the
calculation just depends on the &lt;code&gt;account.entries&lt;/code&gt;. We have an easy way to
update the account via &lt;code&gt;touch&lt;/code&gt;, but other calculation can have dependencies that
are can make the cache expiration more complex.&lt;/p&gt;

&lt;h2&gt;Do you know other problems with this solution?&lt;/h2&gt;

&lt;p&gt;If you have experience with other problems with this solution, please leave a
comment =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Will it save the wrong value?</title>
    <link rel="alternate" href="https://bhserna.com/will-it-save-the-wrong-value"/>
    <id>https://bhserna.com/will-it-save-the-wrong-value</id>
    <published>2023-01-03T23:18:00Z</published>
    <updated>2023-01-03T23:18:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I have already &lt;a href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions.html"&gt;written about how you should be aware about race conditions when saving a computed value&lt;/a&gt;, because you could save a wrong value.&lt;/p&gt;

&lt;p&gt;Here I want to share with you an exercise to help you (and me 😅) get more sensitivity about when an implementation can save a wrong value thanks to race conditions.&lt;/p&gt;

&lt;h2&gt;Your task&lt;/h2&gt;

&lt;p&gt;I will show you some excercises with different ways/methods to calculate the balance and for each method you will have to think if the implementation can save the wrong value due to race conditions or not.&lt;/p&gt;

&lt;p&gt;I am presenting my answer for each problem as guide, but this kind of problems are hard for me also. So, if you see an error please tell me in the comments.&lt;/p&gt;

&lt;h2&gt;Problem description&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="c1"&gt;# we are going to change what happen here&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But the problem is that &lt;strong&gt;the account will need to create many entries concurrently&lt;/strong&gt;, maybe on different background jobs or different requests.&lt;/p&gt;

&lt;p&gt;To simulate concurrency you can use a code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;100&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="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or you can use the tool described in the post: &lt;a href="https://bhserna.com/custom-computed-values-and-race-conditions-examples-rails.html"&gt;Examples to explore possible race conditions when caching custom computed values in Rails&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Exercise 00 - Add balance&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  It can save the wrong value due to race conditions. You can see this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/00_add_balance.rb#L46"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 01 - Sum balance in ruby&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&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;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  It can save the wrong value due to race conditions. You can see this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/01_sum_balance_ruby.rb#L80"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 02 - Sum balance in database&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  It can save the wrong value due to race conditions. You can see this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/02_sum_balance_db.rb#L50"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 03 - Sum balance in database on after touch&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="n"&gt;after_touch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_balance&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  As far as I understand, if you are using Postgres with the default isolation level &lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED"&gt;Read Committed&lt;/a&gt;, you can compute and save the value on an &lt;code&gt;after_touch&lt;/code&gt;.
  &lt;/p&gt;
  &lt;p&gt;
  Apparently &lt;code&gt;after_touch&lt;/code&gt; works something like this:
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;ul&gt;
  &lt;li&gt;There is a change in the association.&lt;/li&gt;
  &lt;li&gt;Within the same transaction, rails updates the associated record (&lt;code&gt;account&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Then it performs the other operation that you have configured (&lt;code&gt;update_balance&lt;/code&gt;).&lt;/li&gt;
  &lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  PostgreSQL uses by default the &lt;a href="https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED"&gt;Read Committed&lt;/a&gt; isolation level.
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  In this mode (as far as I understand), if within a transaction there is an &lt;code&gt;UPDATE&lt;/code&gt; of a row and in another concurrent transaction an &lt;code&gt;UPDATE&lt;/code&gt; of the same row is attempted, PostgreSQL will wait for the first transaction to be committed before continuing with the second one.
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  This makes the operations we perform on &lt;code&gt;after_touch&lt;/code&gt; to be isolated and avoids the problem of saving a wrong error due to another transaction registering another entry for the same account concurrently.
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  You can test more, with this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/03_after_touch.rb"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 04 - Sum balance in database using with_lock&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
      &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  In the example the calculation of the balance and the saving will be in a lock. So, at least this part will always be right.
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  But as we are dividing the process in two steps:
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;ol&gt;
    &lt;li&gt;Creating the entry&lt;/li&gt;
    &lt;li&gt;Calculating and saving the balance&lt;/li&gt;
  &lt;/ol&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  … it could happen that the first time that the balance is saved will be with a result different than 100, because more entries have been created before that moment.
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;p&gt;
  You can check this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/04_with_lock_on_update_balance.rb#L53"&gt;run as an example&lt;/a&gt; of this problem:
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;
Run with ThreadsTransaction (run 1)
[2] Record created: 77
[0] Record created: 76
[3] Record created: 75
[1] Record created: 78
[2] Balance calculated: 400
[2] Balance saved: 400
[1] Balance calculated: 400
[1] Balance saved: 400
[3] Balance calculated: 400
[3] Balance saved: 400
[0] Balance calculated: 400
[0] Balance saved: 400
Output: 400
  &lt;/pre&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 05 - Sum balance in database using with_lock on after touch&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;
  &lt;span class="n"&gt;after_touch&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_balance&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
      &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  Taking in account what I said in the exercises 03 and 04, the after touch wraps the call to &amp;ldquo;update_balance&amp;rdquo; in the same transaction of the entry creation. So, maybe the lock is not necessary.
  &lt;/p&gt;
  &lt;p&gt;
  You can test more, with this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/05_with_lock_after_touch.rb"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 06 - Sum balance in database using with_lock on full operation&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;with_lock&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;update_balance&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  In this example we are wrapping each entry creation process on a lock. It, should always produce the right balance.
  &lt;/p&gt;
  &lt;p&gt;
  You can test more, with this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/06_with_lock_full_operation.rb"&gt;example&lt;/a&gt;.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Exercise 07 - Updating counters&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_counters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
  &lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
  &lt;p&gt;
  This is very similar to the exercise 04, we are (kind of) dividing the process in two steps:
  &lt;/p&gt;&lt;/p&gt;

&lt;p&gt;&lt;ol&gt;
    &lt;li&gt;Creating the entry&lt;/li&gt;
    &lt;li&gt;Calculating and saving the balance&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;
  So, it could happen that the first time that the balance is saved will be with a result different than 100, because more entries have been created before that moment.
  &lt;/p&gt;
  &lt;p&gt;
  You can check this &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/07_update_counters.rb#L42"&gt;run as an example&lt;/a&gt; of this problem.
  &lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Saving incorrect computed values thanks to race conditions</title>
    <link rel="alternate" href="https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions"/>
    <id>https://bhserna.com/saving-incorrect-computed-values-thanks-to-race-conditions</id>
    <published>2022-12-22T23:31:00Z</published>
    <updated>2022-12-22T23:31:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When saving computed values in the database in your rails app, you must be aware that is possible to find unexpected errors in the result thanks to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want to help you visualize how race conditions can make you save incorrect values even when your calculation is correct.&lt;/p&gt;

&lt;p&gt;I will show you how it can happen with different ways of saving the same value and show you a tool that you can use to explore a little more.&lt;/p&gt;

&lt;h2&gt;Using the account balance as an example&lt;/h2&gt;

&lt;p&gt;Imagine that you have an &lt;code&gt;Account&lt;/code&gt; record that has many &lt;code&gt;entries&lt;/code&gt;, and you want to update the &lt;code&gt;balance&lt;/code&gt; each time an &lt;code&gt;Entry&lt;/code&gt; is created. The &lt;code&gt;balance&lt;/code&gt; is the sum of the &lt;code&gt;amount&lt;/code&gt; of each &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="c1"&gt;# we are going to change what happen here&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Adding to the current state&lt;/h2&gt;

&lt;p&gt;One possible solution to calculate the balance, is start with zero and sum the &lt;code&gt;amount&lt;/code&gt; of each new &lt;code&gt;entry&lt;/code&gt; to the current &lt;code&gt;balance&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;p&gt;That looks ok, and if you write something like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can expect a printed balance of &lt;code&gt;400&lt;/code&gt;. If you run the code that will be correct.&lt;/p&gt;

&lt;p&gt;But if instead of creating the entries serially, you create each entry in a different process or thread, concurrently, maybe on different background jobs, different requests or with code like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;
&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;100&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="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now the saved balance at the end of the process could have a wrong value.&lt;/p&gt;

&lt;p&gt;… Can you see why?&lt;/p&gt;

&lt;p&gt;If you don’t know why, don’t worry, it is not obvious at all!&lt;/p&gt;

&lt;h2&gt;Let’s try to understand why it can save a wrong value&lt;/h2&gt;

&lt;p&gt;Let’s start by breaking what is happening each time we create an entry.&lt;/p&gt;

&lt;p&gt;Each time we call &lt;code&gt;account.create_entry&lt;/code&gt; we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the entry&lt;/li&gt;
&lt;li&gt;Calculate the new balance&lt;/li&gt;
&lt;li&gt;Save the balance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/00_add_balance.rb#L31"&gt;create all entries serially&lt;/a&gt;, each step happen like in the next code block:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;# Run with SerialTransaction
# [0] Record created
# [0] Balance calculated: 100
# [0] Balance saved: 100
# [1] Record created
# [1] Balance calculated: 200
# [1] Balance saved: 200
# [2] Record created
# [2] Balance calculated: 300
# [2] Balance saved: 300
# [3] Record created
# [3] Balance calculated: 400
# [3] Balance saved: 400
# Output: 400
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each step is in the &amp;ldquo;expeted order&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;Each call to &lt;code&gt;account.create_entry&lt;/code&gt; is represented by an index inside brackets, like &lt;code&gt;[0]&lt;/code&gt;, &lt;code&gt;[1]&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;But when we create the entries concurrently, we will see that the steps of each call to &lt;code&gt;account.create_entry&lt;/code&gt; will be mixed.&lt;/p&gt;

&lt;p&gt;For example the next code block is an &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/00_add_balance.rb#L46"&gt;example run&lt;/a&gt; of running each call to &lt;code&gt;account.create_entry&lt;/code&gt; on a new thread:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;# Run with ThreadsTransaction
# [3] Record created: 08
# [3] Balance calculated: 100
# [1] Record created: 09
# [1] Balance calculated: 100
# [0] Record created: 07
# [0] Balance calculated: 100
# [2] Record created: 10
# [2] Balance calculated: 100
# [0] Balance saved: 100
# [1] Balance saved: 100
# [3] Balance saved: 100
# [2] Balance saved: 100
# Output: 100
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And as you can see the final output is &lt;code&gt;100&lt;/code&gt; not &lt;code&gt;400&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;If we see with care, we can see some funky things, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The records where not created in order&lt;/li&gt;
&lt;li&gt;All balance calculations where &lt;code&gt;100&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Every time we saved the balance we always saved &lt;code&gt;100&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why do you think that every balance calculation were &lt;code&gt;100&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;The reason is that the four calls to &lt;code&gt;account.create_entry&lt;/code&gt;, start at the same time and at that time the current balance is 0 for each call, then we “correctly” sum the &lt;code&gt;entry.amount&lt;/code&gt; of &lt;code&gt;100&lt;/code&gt; and the total was &lt;code&gt;100&lt;/code&gt; every time.&lt;/p&gt;

&lt;h2&gt;Recalculating the value&lt;/h2&gt;

&lt;p&gt;But what if instead of using the currently saved balance, we recalculate the balance?&lt;/p&gt;

&lt;p&gt;Maybe with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we break what is happening each time we create an entry, we can see that we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the entry&lt;/li&gt;
&lt;li&gt;Calculate the new balance&lt;/li&gt;
&lt;li&gt;Save the balance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, just as in the previous example!&lt;/p&gt;

&lt;p&gt;And when we &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/02_sum_balance_db.rb#L50"&gt;run the example&lt;/a&gt; creating each entry on a different thread, we can see that it can also can have problems:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Run with ThreadsTransaction&lt;/span&gt;
&lt;span class="c1"&gt;# [1] Record created: 23&lt;/span&gt;
&lt;span class="c1"&gt;# [1] Balance calculated: 100&lt;/span&gt;
&lt;span class="c1"&gt;# [2] Record created: 25&lt;/span&gt;
&lt;span class="c1"&gt;# [0] Record created: 26&lt;/span&gt;
&lt;span class="c1"&gt;# [2] Balance calculated: 300&lt;/span&gt;
&lt;span class="c1"&gt;# [3] Record created: 24&lt;/span&gt;
&lt;span class="c1"&gt;# [0] Balance calculated: 400&lt;/span&gt;
&lt;span class="c1"&gt;# [3] Balance calculated: 400&lt;/span&gt;
&lt;span class="c1"&gt;# [1] Balance saved: 100&lt;/span&gt;
&lt;span class="c1"&gt;# [0] Balance saved: 400&lt;/span&gt;
&lt;span class="c1"&gt;# [3] Balance saved: 400&lt;/span&gt;
&lt;span class="c1"&gt;# [2] Balance saved: 300&lt;/span&gt;
&lt;span class="c1"&gt;# Output: 300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Although &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions/blob/main/examples/02_sum_balance_db.rb#L65"&gt;sometimes we are going to get the right value&lt;/a&gt;… We can’t know when it is going to be wrong.&lt;/p&gt;

&lt;h2&gt;Be aware race conditions&lt;/h2&gt;

&lt;p&gt;Now (I hope) you understand that saving a computed value that can be updated by processes or threads running concurrently, maybe on different background jobs or different web requests, can cause race conditions and incorrect saved values.&lt;/p&gt;

&lt;h2&gt;How to solve these kind of problems?&lt;/h2&gt;

&lt;p&gt;I really don’t know 😅&lt;/p&gt;

&lt;p&gt;There are different techniques that you can try but I don’t know a way that will always work and won’t give you troubles in other way.&lt;/p&gt;

&lt;p&gt;Still, here are some options that you can try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t save the value&lt;/li&gt;
&lt;li&gt;&lt;a href="touch-fragment-caching-to-avoid-race-conditions-saving-computed-values.html"&gt;Use fragment caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/pick-a-safe-previous-date-to-avoid-race-conditions-saving-computed-values.html"&gt;Pick a &amp;ldquo;safe&amp;rdquo; previous date&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use domain knowledge, to save the value when concurrency will not be present&lt;/li&gt;
&lt;li&gt;&lt;a href="/use-with_lock-to-handle-race-conditions-saving-computed-values.html"&gt;Use database locks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Use after touch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;A tool to explore more&lt;/h2&gt;

&lt;p&gt;I built a repo with the setup tor run the examples of this post (and some more), that can help you explore other solutions that could have errors and others that won’t.&lt;/p&gt;

&lt;p&gt;You can get more details on the post:
&lt;a href="https://bhserna.com/custom-computed-values-and-race-conditions-examples-rails.html"&gt;Examples to explore possible race conditions when caching custom computed values in Rails&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Examples to explore possible race conditions when caching custom computed values in Rails</title>
    <link rel="alternate" href="https://bhserna.com/custom-computed-values-and-race-conditions-examples-rails"/>
    <id>https://bhserna.com/custom-computed-values-and-race-conditions-examples-rails</id>
    <published>2022-12-07T23:42:00Z</published>
    <updated>2022-12-07T23:42:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;In Rails, sometimes you will need to save counts or custom computed values, where the default counter cache will not be enough.&lt;/p&gt;

&lt;p&gt;Maybe you want to…&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Update a counter cache when a value change and not only when the association is created or deleted.&lt;/li&gt;
&lt;li&gt;Have a counter cache for complex “has many through” associations.&lt;/li&gt;
&lt;li&gt;Keep a count for a scope of the association, like just the “positive reactions”, or the “completed orders”.&lt;/li&gt;
&lt;li&gt;Cache a sum or another calculation like the “account balance”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are different techniques for caching this kind of values, but sometimes this type of calculation are prone to race conditions.&lt;/p&gt;

&lt;p&gt;Here I want share a tool to help you understand why caching this kind of values are prone to race conditions, analyzing different ways to solve the “account balance” problem. Hoping it could help you extrapolate to other situations.&lt;/p&gt;

&lt;h2&gt;A list of runnable examples&lt;/h2&gt;

&lt;p&gt;The tool that I am talking about is a list of runnable examples that you can find in the repo: &lt;a href="https://github.com/bhserna/custom_computed_values_and_race_conditions"&gt;github.com/bhserna/custom_computed_values_and_race_conditions&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;How can this examples help you?&lt;/h2&gt;

&lt;p&gt;You will be able to compare the threads of execution for each example with an output like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;# Run with ThreadsTransaction
# [1] Record created: 15
# [1] Balance calculated: 100
# [2] Record created: 16
# [2] Balance calculated: 200
# [3] Record created: 17
# [3] Balance calculated: 300
# [0] Record created: 18
# [0] Balance calculated: 400
# [2] Balance saved: 200
# [0] Balance saved: 400
# [1] Balance saved: 100
# [3] Balance saved: 300
# Output: 300
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each example will run a &amp;ldquo;transaction&amp;rdquo; (not db transaction) where it will create four &lt;code&gt;entries&lt;/code&gt; in an &lt;code&gt;account&lt;/code&gt; with an &lt;code&gt;amount&lt;/code&gt; of &lt;code&gt;100&lt;/code&gt; and calculate the &lt;code&gt;balance&lt;/code&gt; of the &lt;code&gt;account&lt;/code&gt; after the creation of each &lt;code&gt;entry&lt;/code&gt;. Expecting a final balance of &lt;code&gt;400&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each example calculates the &lt;code&gt;balance&lt;/code&gt; in a slightly different way. For example one adds the amount of each entry to the current &lt;code&gt;account.balance&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;amount&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And other sums the &lt;code&gt;amount&lt;/code&gt; of all &lt;code&gt;account.entries&lt;/code&gt; with ruby, each time that an entry is created, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&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;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_balance&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_balance&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;balance: &lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Entry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ExampleRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;balance&lt;/span&gt;
    &lt;span class="n"&gt;sum&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;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In other examples the sum is done in the database, other examples use &lt;code&gt;with_lock&lt;/code&gt; in different places, and other examples use the class method &lt;code&gt;ExampleRecord.update_counters&lt;/code&gt; from rails.&lt;/p&gt;

&lt;p&gt;Each example will run the &amp;ldquo;transaction&amp;rdquo; using three different strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SerialTransaction&lt;/code&gt; - That will create the entries serially in the same thread.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ThreadsTransaction&lt;/code&gt; - That will create each entry and update the balance in a different thread.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ForksTransaction&lt;/code&gt; - That will create each entry and update the balance in a different process, using fork.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the &lt;code&gt;ThreadsTransaction&lt;/code&gt; and &lt;code&gt;ForksTransactions&lt;/code&gt; it will run the transaction until a run returns a different &lt;code&gt;balance&lt;/code&gt; than the expected of &lt;code&gt;400&lt;/code&gt;, or if the number of runs equals the parameter &lt;code&gt;max_runs_per_transaction_type&lt;/code&gt; that has &lt;code&gt;5&lt;/code&gt; as default value.&lt;/p&gt;

&lt;p&gt;When you run an example you will find an output like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;# Run with SerialTransaction
# [0] Record created: 03
# [0] Balance calculated: 100
# [0] Balance saved: 100
# [1] Record created: 04
# [1] Balance calculated: 200
# [1] Balance saved: 200
# [2] Record created: 05
# [2] Balance calculated: 300
# [2] Balance saved: 300
# [3] Record created: 06
# [3] Balance calculated: 400
# [3] Balance saved: 400
# Output: 400
#
# Run with ThreadsTransaction
# [1] Record created: 07
# [2] Record created: 08
# [1] Balance calculated: 100
# [0] Record created: 09
# [2] Balance calculated: 300
# [3] Record created: 10
# [0] Balance calculated: 300
# [3] Balance calculated: 400
# [1] Balance saved: 100
# [2] Balance saved: 300
# [0] Balance saved: 300
# [3] Balance saved: 400
# Output: 400
#
# Run with ThreadsTransaction
# [1] Record created: 11
# [1] Balance calculated: 100
# [2] Record created: 12
# [2] Balance calculated: 200
# [3] Record created: 13
# [3] Balance calculated: 300
# [0] Record created: 14
# [0] Balance calculated: 400
# [2] Balance saved: 200
# [3] Balance saved: 300
# [1] Balance saved: 100
# [0] Balance saved: 400
# Output: 400
#
# Run with ThreadsTransaction
# [1] Record created: 15
# [1] Balance calculated: 100
# [2] Record created: 16
# [2] Balance calculated: 200
# [3] Record created: 17
# [3] Balance calculated: 300
# [0] Record created: 18
# [0] Balance calculated: 400
# [2] Balance saved: 200
# [0] Balance saved: 400
# [1] Balance saved: 100
# [3] Balance saved: 300
# Output: 300
#
# Run with ForksTransaction
# [2] Record created: 20
# [1] Record created: 19
# [2] Balance calculated: 200
# [0] Record created: 21
# [1] Balance calculated: 300
# [0] Balance calculated: 300
# [3] Record created: 22
# [3] Balance calculated: 400
# [3] Balance saved: 400
# [2] Balance saved: 200
# [0] Balance saved: 300
# [1] Balance saved: 300
# Output: 300
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To explain what that means, we can zoom in to the output of the last run in the &lt;code&gt;ThreadsTransaction&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;# Run with ThreadsTransaction
# [1] Record created: 15
# [1] Balance calculated: 100
# [2] Record created: 16
# [2] Balance calculated: 200
# [3] Record created: 17
# [3] Balance calculated: 300
# [0] Record created: 18
# [0] Balance calculated: 400
# [2] Balance saved: 200
# [0] Balance saved: 400
# [1] Balance saved: 100
# [3] Balance saved: 300
# Output: 300
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The number in brackets (&lt;code&gt;[]&lt;/code&gt;) is an index of the current thread of execution of each created &lt;code&gt;entry&lt;/code&gt;. So you will be able to compare how each part of the process is executed.&lt;/p&gt;

&lt;p&gt;For example in the previous log we can see some, maybe unexpected, things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The thread [0] created the record after the other three threads.&lt;/li&gt;
&lt;li&gt;The thread [3] calculates the balance before the last record is created, but is the last that saves the balance, giving us a bad result at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;How to run the examples&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install the dependencies&lt;/strong&gt; with &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database setup&lt;/strong&gt; - run the command:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby db/setup.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Run the examples&lt;/strong&gt; with &lt;code&gt;ruby examples/&amp;lt;file name&amp;gt;&lt;/code&gt;. For example:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby example/00_example.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Change the seeds&lt;/strong&gt;  on &lt;code&gt;db/seeds.rb&lt;/code&gt; and re-run &lt;code&gt;ruby db/setup.rb&lt;/code&gt; to test different scenarios.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Broadcast multiple actions with broadcast_render</title>
    <link rel="alternate" href="https://bhserna.com/broadcast-multiple-actions-with-broadcast_render"/>
    <id>https://bhserna.com/broadcast-multiple-actions-with-broadcast_render</id>
    <published>2022-09-19T22:28:00Z</published>
    <updated>2022-09-19T22:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you are creating a record (let’s say a tweet), and on creation, you want to add the tweet to the page and also update the tweets count in the page&amp;hellip;&lt;/p&gt;

&lt;p&gt;But you want to update the content, not just for the current user, but for all the users in the page, you want to broadcast the changes.&lt;/p&gt;

&lt;p&gt;How would you do it?&lt;/p&gt;

&lt;p&gt;Well, here I want to show you how you can use &lt;code&gt;broadcast_render&lt;/code&gt; or its &lt;code&gt;_later&lt;/code&gt; variations, to do it.&lt;/p&gt;

&lt;h2&gt;Example video&lt;/h2&gt;

&lt;p&gt;You will be able to do something like this:&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 60.70826306913997%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/b4cf7e331ca9489781e211e3cd484564" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;Use broadcast_render_later_to&lt;/h2&gt;

&lt;p&gt;This method will help you render a turbo stream template with the current model as the local variable, so you can send something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="s2"&gt;"new_tweet"&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"tweets/form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweet: &lt;/span&gt;&lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt; &lt;span class="s2"&gt;"tweets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tweet&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;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="s2"&gt;"tweets_count"&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"tweets/count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweets: &lt;/span&gt;&lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;p&gt;If you do:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_render_later_to&lt;/span&gt; &lt;span class="ss"&gt;:tweets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"tweets/on_create"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Why you should prefer broadcast_render_later_to over broadcast_render_to?&lt;/h2&gt;

&lt;p&gt;As normally these methods will render some templates and the send them, if you use &lt;code&gt;broadcast_render_to&lt;/code&gt; as a reaction to a controller action, you will block the response of the controller action until the rendering is done.&lt;/p&gt;

&lt;p&gt;Instead if you use &lt;code&gt;broadcast_render_later_to&lt;/code&gt; the rendering and broadcast would be executed on a background job, and won&amp;rsquo;t block the controller&amp;rsquo;s response.&lt;/p&gt;

&lt;p&gt;When you are destroying a record (as far as I understand) you won&amp;rsquo;t be able to use &lt;code&gt;broadcast_render_later_to&lt;/code&gt; because as the record is already destroyed, it won&amp;rsquo;t be able to fetch the record on the job. So, after destroy, you could use &lt;code&gt;broadcast_render_to&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Let’s see the full example&lt;/h2&gt;

&lt;p&gt;For the example of the video&amp;hellip;&lt;/p&gt;

&lt;p&gt;You can have a tweets/index like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream_from&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;tweets&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweets: &lt;/span&gt;&lt;span class="vi"&gt;@tweets&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Tweets&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweet: &lt;/span&gt;&lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tweets"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@tweets&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It needs the &lt;code&gt;turbo_stream_from :tweets&lt;/code&gt; to listen to the broadcasts that you will produce.&lt;/p&gt;

&lt;p&gt;The form could be a &amp;ldquo;standard&amp;rdquo; form like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_area&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;p&gt;Just look that in this case I am using &lt;code&gt;dom_id(tweet)&lt;/code&gt; as the form id. In that way, for non persisted records, the id will be &lt;code&gt;new_tweet&lt;/code&gt; and it could be replaced with &lt;code&gt;turbo_stream.replace &amp;quot;new_tweet&amp;quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then in the controller you can create the tweet and do the broadcast there, without any other response.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
  &lt;span class="vi"&gt;@tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_render_later_to&lt;/span&gt; &lt;span class="ss"&gt;:tweets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"tweets/on_create"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The partial &lt;code&gt;tweets/on_create.turbo_stream.erb&lt;/code&gt; could look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="s2"&gt;"new_tweet"&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"tweets/form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweet: &lt;/span&gt;&lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt; &lt;span class="s2"&gt;"tweets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tweet&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;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt; &lt;span class="s2"&gt;"tweets_count"&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"tweets/count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;tweets: &lt;/span&gt;&lt;span class="no"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;p&gt;It will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace the form with a new form to clear the textarea&lt;/li&gt;
&lt;li&gt;Preprend the new tweet&lt;/li&gt;
&lt;li&gt;Update the &amp;ldquo;tweets count&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you think that is better for your use case to render from the model, you can do it on an &lt;code&gt;after_create_commit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tweet&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;after_create_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;broadcast_render_later_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"tweets/on_create"&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;h2&gt;Play with the code&lt;/h2&gt;

&lt;p&gt;Here is code of the example app that I used for the video and the examples, go and play with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/bhserna/broadcast_render_example"&gt;github.com/bhserna/broadcast_render_example&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a dynamic data grid with search and filters using rails, hotwire and ransack</title>
    <link rel="alternate" href="https://bhserna.com/building-data-grid-with-search-rails-hotwire-ransack"/>
    <id>https://bhserna.com/building-data-grid-with-search-rails-hotwire-ransack</id>
    <published>2022-09-03T12:24:00Z</published>
    <updated>2022-09-03T12:24:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you want to build powerful admin interfaces with little code, but you are not sure if you want to jump into a full admin solution like Active Admin, Administrate or Avo?&lt;/p&gt;

&lt;p&gt;Here I want to show you an alternative!&lt;/p&gt;

&lt;p&gt;A step by step guide to build a dynamic data grid with search and filters for your admin interfaces, using rails, the ransack gem and hotwire.&lt;/p&gt;

&lt;h2&gt;What are you going to build?&lt;/h2&gt;

&lt;p&gt;You are goint to build a paginated table with four filters and sortable columns.&lt;/p&gt;

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

&lt;div style="position: relative; padding-bottom: 63.15789473684211%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/fcf06c14689f4d359cf5213931746cfe" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;Step 1: A non-dynamic paginated table&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/bhserna/dynamic_data_grid_hotwire_ransack/commit/25bab8b1ea9975a489a7aafec1fd639c03e40130"&gt;You can find the full commit here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step is a common rails index with the pagy gem to build the pagination.&lt;/p&gt;

&lt;h3&gt;Create the record&lt;/h3&gt;

&lt;p&gt;For the example we are going to use three columns &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;price&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProducts&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="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decimal&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And &lt;code&gt;category&lt;/code&gt; is going to be an &lt;code&gt;enum&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:toys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:electronics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:food&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;h3&gt;Declare the routes&lt;/h3&gt;

&lt;p&gt;We are going to create a &lt;code&gt;products&lt;/code&gt; resource with only the &lt;code&gt;index&lt;/code&gt; route.&lt;/p&gt;

&lt;p&gt;And to make it easy to test we can define the &lt;code&gt;root&lt;/code&gt; as a &lt;code&gt;redirect&lt;/code&gt; to &lt;code&gt;/products&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/products"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:index&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;h3&gt;The controller&lt;/h3&gt;

&lt;p&gt;For this phase of the project we are going to paginate all products, with 10 items per page.&lt;/p&gt;

&lt;p&gt;Once you have the setup for the &lt;code&gt;pagy&lt;/code&gt; gem, the controller code will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagy&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;all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;And then add the views…&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;index.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Products&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Category&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Price&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="n"&gt;pagy_nav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the &lt;code&gt;_product.html.erb&lt;/code&gt; partial&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&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;tr&lt;/span&gt; &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;category&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_to_currency&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/td&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;h3&gt;Enough CSS&lt;/h3&gt;

&lt;p&gt;To make it look descent you can put this css on your &lt;code&gt;application.css&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"https://unpkg.com/open-props"&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="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-sans&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.table-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;tbody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-lineheight-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;th&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;th&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.pagy-nav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.pagy-nav&lt;/span&gt; &lt;span class="nc"&gt;.page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-1&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;h2&gt;Step 2: Order columns with sort_link&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/bhserna/dynamic_data_grid_hotwire_ransack/commit/619e01647b108ad93e45bd81bd7f8a142992944c"&gt;You can find the full commit here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make the columns sortable with ransack you need to use the helper &lt;code&gt;sort_link&lt;/code&gt; on each &lt;code&gt;th&lt;/code&gt; from the table:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then use the &lt;code&gt;ransack&lt;/code&gt; method to create a &lt;code&gt;Ransack::Search&lt;/code&gt; that you should assign to the instance variable &lt;code&gt;@q&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ransack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;distinct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to set a default sort you can do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name asc"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the example app I put the code in a private method like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ransack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;distinct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;set_default_sort&lt;/span&gt;
  &lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kp"&gt;private&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_default_sort&lt;/span&gt;
  &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name asc"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Step 3: Search with ransack&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/bhserna/dynamic_data_grid_hotwire_ransack/commit/04c3266a101010f7a07673b10a4460913c77cb8a"&gt;You can find the full commit here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To search with ransack you need to follow the convention defined in the &lt;a href="https://activerecord-hackery.github.io/ransack/getting-started/simple-mode/#form-helper"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the documentation you can also find &lt;a href="https://activerecord-hackery.github.io/ransack/getting-started/search-matches/"&gt;a list with all possible matchers/predicates&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the example we have four filters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;search_field&lt;/code&gt; for &lt;code&gt;name_cont&lt;/code&gt;, that match if the &lt;code&gt;name&lt;/code&gt; contains the given value.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;select&lt;/code&gt; for &lt;code&gt;category_eq&lt;/code&gt; with the list of possible categories, that match if the &lt;code&gt;category&lt;/code&gt; equals the selected value.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;search_field&lt;/code&gt; for &lt;code&gt;price_gt&lt;/code&gt;, that match if the &lt;code&gt;price&lt;/code&gt; is greater than the given value.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;search_field&lt;/code&gt; for &lt;code&gt;price_lt&lt;/code&gt;, that match if the &lt;code&gt;price&lt;/code&gt; is less than the given value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To implement it you need to write this erb.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;search_form_for&lt;/span&gt; &lt;span class="vi"&gt;@q&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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_gt&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;p&gt;To style the form you can use this css:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt; &lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;search&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
 &lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;-moz-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;submit&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--radius-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-size-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;submit&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nc"&gt;.table-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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;h2&gt;Step 4: Using turbo to send the form when somthing changes&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/bhserna/dynamic_data_grid_hotwire_ransack/commit/29b58097d9e81827b3c2d00179e53290cf92eee6"&gt;You can find the full commit here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can remove the button and instead send the form when something in it changes.&lt;/p&gt;

&lt;p&gt;To do it you can create an stimulus controller that calls “requestSubmit()” when something changes.&lt;/p&gt;

&lt;p&gt;It could be something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="c1"&gt;// filters_controller.js&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"@hotwired/stimulus"&lt;/span&gt;

 &lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestSubmit&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now you can update the form to define the controller and to reference a turbo frame to update. Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;search_form_for&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"data-controller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-frame"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then on each control you should define an action that will call “filters#submit” and remove the submit button.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;search_form_for&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"data-controller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-frame"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_gt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;p&gt;You will need to wrap the table and pagination in a &lt;code&gt;turbo_frame_tag&lt;/code&gt; that match what you defined in the form, in this case you need to call it &lt;code&gt;&amp;quot;table&amp;quot;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="n"&gt;pagy_nav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&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="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can update the css of your &lt;code&gt;form&lt;/code&gt; to take the full width and give the same space to each control:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-auto-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-auto-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;1fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size-2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-end&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;h2&gt;Step 6: Adding a “loading” indicator&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/bhserna/dynamic_data_grid_hotwire_ransack/commit/0d308269491084dd27dcd32cf2bfa9086c5803e5"&gt;You can find the full commit here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add a loading indicator you can use the attribute &lt;code&gt;busy&lt;/code&gt; on the &lt;code&gt;turbo-frame&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is a boolean attribute managed by turbo. It is toggled to be present when a &lt;code&gt;turbo-frame&lt;/code&gt;-initiated request starts, and toggled false when the request ends.&lt;/p&gt;

&lt;p&gt;Then you can add an tag with the &lt;code&gt;loading&lt;/code&gt; class on the turbo frame:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/p&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;p&gt;And add some css to toggle the visibility based on the &lt;code&gt;busy&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nc"&gt;.loading&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;turbo-frame&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;busy&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.loading&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;visible&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;h2&gt;Summary&lt;/h2&gt;

&lt;p&gt;This is the code that you need to build a dynamic paginated data-grid with ransack and hotwire.&lt;/p&gt;

&lt;p&gt;On the controller you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call ransack with &lt;code&gt;params[:q]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pass the result to pagy&lt;/li&gt;
&lt;li&gt;And if you want, you can set a default order&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ransack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:q&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;distinct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;set_default_sort&lt;/span&gt;
    &lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pagy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;items: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_default_sort&lt;/span&gt;
    &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name asc"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&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;p&gt;Then on the view you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass &lt;code&gt;@q&lt;/code&gt; to the &lt;code&gt;serch_form_for&lt;/code&gt; helper.&lt;/li&gt;
&lt;li&gt;Define the &lt;code&gt;filters&lt;/code&gt; controller on the form.&lt;/li&gt;
&lt;li&gt;Tell the form to act on the &lt;code&gt;table&lt;/code&gt; turbo frame.&lt;/li&gt;
&lt;li&gt;Define an action to &lt;code&gt;filters#submit&lt;/code&gt; on each input.&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;sort_link&lt;/code&gt; on each &lt;code&gt;th&lt;/code&gt; that you want.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Products&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;search_form_for&lt;/span&gt; &lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"data-controller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-turbo-frame"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:name_cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="ss"&gt;:category_eq&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;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_gt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_field&lt;/span&gt; &lt;span class="ss"&gt;:price_lt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"data-action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"loading"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;sort_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@products&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="n"&gt;pagy_nav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&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="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To submit the form on &lt;code&gt;filters#submit&lt;/code&gt; you need a controller like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"@hotwired/stimulus"&lt;/span&gt;

&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestSubmit&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;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Fetching the top n per group with window functions</title>
    <link rel="alternate" href="https://bhserna.com/fetching-the-top-n-per-group-with-window-functions"/>
    <id>https://bhserna.com/fetching-the-top-n-per-group-with-window-functions</id>
    <published>2022-08-22T22:34:00Z</published>
    <updated>2022-08-22T22:34:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you ever needed to get the most recent N posts for each user in rails, but didn&amp;rsquo;t know how to do it without using map?&lt;/p&gt;

&lt;p&gt;Or maybe something similar like:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The first or last X comments for each post&lt;/li&gt;
&lt;li&gt;The first or last Y payments for each customer&lt;/li&gt;
&lt;li&gt;The first or last Z reviews for each customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes could be ok to just fetch all elements and filter with ruby, but sometimes it is not possible. Also it can cause n+1 queries if your are not careful.&lt;/p&gt;

&lt;p&gt;Here I want to show you how you can solve this problem using window functions.&lt;/p&gt;

&lt;h2&gt;An example problem&lt;/h2&gt;

&lt;p&gt;Imagine you have a &lt;code&gt;User&lt;/code&gt; and a &lt;code&gt;Post&lt;/code&gt; record and a &lt;code&gt;User&lt;/code&gt; has many &lt;code&gt;Posts&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you want to fetch a list of &lt;code&gt;users&lt;/code&gt; with their last 3 &lt;code&gt;posts&lt;/code&gt;, but for performance reasons you don’t want to fetch all &lt;code&gt;posts&lt;/code&gt; for all &lt;code&gt;users&lt;/code&gt; in the list, you just want to ask the database for the 3 &lt;code&gt;posts&lt;/code&gt; per &lt;code&gt;user&lt;/code&gt; that you want to use.&lt;/p&gt;

&lt;h2&gt;Building the SQL query&lt;/h2&gt;

&lt;p&gt;First we need to order the &lt;code&gt;posts&lt;/code&gt; and produce a rank for each user.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dense_rank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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="k"&gt;DESC&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;posts_rank&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then use that query as a subquery to now select just the &lt;code&gt;posts&lt;/code&gt; with rank less than or equals to 3.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="p"&gt;(&lt;/span&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dense_rank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;posts_rank&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ranked_posts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;posts_rank&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will return a list with just the last three &lt;code&gt;posts&lt;/code&gt; for each &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Scope with a fixed n&lt;/h2&gt;

&lt;p&gt;Now we need to pass this query to rails in a way that it could build &lt;code&gt;Post&lt;/code&gt; objects from this information.&lt;/p&gt;

&lt;p&gt;This is how I made it work.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;select_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      posts.*, dense_rank() OVER (
        PARTITION BY posts.user_id
        ORDER BY posts.id DESC
      ) AS posts_rank
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;ranked_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;"posts_rank &amp;lt;= 3"&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;p&gt;From the sql we need the part that selects the &lt;code&gt;posts&lt;/code&gt; with the rank, to then use it in the rails &lt;code&gt;select&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Then we use &lt;code&gt;from&lt;/code&gt; to use the &lt;code&gt;ranked_posts&lt;/code&gt; relation as subquery and then use &lt;code&gt;where&lt;/code&gt; to define the &lt;code&gt;post_rank&lt;/code&gt; that we want to filter.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;post_rank&lt;/code&gt; is what defines the “n”. The number of posts per user that we want to fetch.&lt;/p&gt;

&lt;p&gt;At the end the sql it builds is very similar to our original sql:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&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="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dense_rank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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="k"&gt;DESC&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;posts_rank&lt;/span&gt;
  &lt;span class="k"&gt;FROM&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;posts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;posts_rank&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Scope with a variable n&lt;/h2&gt;

&lt;p&gt;If you want to make the &amp;ldquo;n&amp;rdquo; variable, you can pass it as a parameter to the &lt;code&gt;where&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;select_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      posts.*, dense_rank() OVER (
        PARTITION BY posts.user_id
        ORDER BY posts.id DESC
      ) AS posts_rank
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;ranked_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;"posts_rank &amp;lt;= ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Put it in a has_many association&lt;/h2&gt;

&lt;p&gt;Now that you have build the scope, you can build a has_many association passing that scope, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you will be able to preload just the last_posts like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;5&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="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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_posts&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Code to run the example&lt;/h2&gt;

&lt;p&gt;Here is the full code that you can use to run with the Active Record Playground Runner.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;add_index&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="ss"&gt;:user_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_list_for_each_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&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="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&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="n"&gt;models&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"SQL query"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
    SELECT * FROM (
      SELECT posts.*, dense_rank() OVER (
        PARTITION BY posts.user_id
        ORDER BY posts.id DESC
      ) AS posts_rank
      FROM posts
    ) AS ranked_posts
    WHERE posts_rank &amp;lt;= 3
&lt;/span&gt;&lt;span class="no"&gt;  SQL&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&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;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"With a fixed n"&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;select_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        posts.*, dense_rank() OVER (
          PARTITION BY posts.user_id
          ORDER BY posts.id DESC
        ) AS posts_rank
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;ranked_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;"posts_rank &amp;lt;= 3"&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;span class="n"&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;last_n_per_user&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="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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="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;:id&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;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"With a variable n"&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;select_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        posts.*, dense_rank() OVER (
          PARTITION BY posts.user_id
          ORDER BY posts.id DESC
        ) AS posts_rank
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;ranked_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;"posts_rank &amp;lt;= ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&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;last_n_per_user&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="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="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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="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;:id&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;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"In a has many association"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;select_sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        posts.*, dense_rank() OVER (
          PARTITION BY posts.user_id
          ORDER BY posts.id DESC
        ) AS posts_rank
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;ranked_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;select_sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;"posts_rank &amp;lt;= ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_posts&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;:id&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;p&gt;You can find the code also on: &lt;a href="https://github.com/bhserna/last_n_per_user_window_functions"&gt;github.com/bhserna/last_n_per_user_window_functions&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fetching the top n per group with a lateral join with rails</title>
    <link rel="alternate" href="https://bhserna.com/fetching-the-top-n-per-group-with-a-lateral-join-with-rails"/>
    <id>https://bhserna.com/fetching-the-top-n-per-group-with-a-lateral-join-with-rails</id>
    <published>2022-08-16T22:29:00Z</published>
    <updated>2022-08-16T22:29:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you ever needed to get the most recent N posts for each user in rails, but didn&amp;rsquo;t know how to do it?&lt;/p&gt;

&lt;p&gt;Or maybe something similar like:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The first or last X comments for each post&lt;/li&gt;
&lt;li&gt;The first or last Y payments for each customer&lt;/li&gt;
&lt;li&gt;The first or last Z reviews for each customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes it could be ok to just fetch all elements and filter with ruby, but sometimes it is not possible. Also it can cause n+1 queries if your are not careful.&lt;/p&gt;

&lt;p&gt;Here I want to show you how you can solve this problem using a lateral join.&lt;/p&gt;

&lt;h2&gt;An example problem&lt;/h2&gt;

&lt;p&gt;Imagine you have a &lt;code&gt;User&lt;/code&gt; and a &lt;code&gt;Post&lt;/code&gt; record and a &lt;code&gt;User&lt;/code&gt; has many &lt;code&gt;Posts&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you want to fetch a list of &lt;code&gt;users&lt;/code&gt; with their last 3 &lt;code&gt;posts&lt;/code&gt;, but for performance reasons you don’t want to fetch all &lt;code&gt;posts&lt;/code&gt; for all &lt;code&gt;users&lt;/code&gt; in the list, you just want to ask the database for the 3 &lt;code&gt;posts&lt;/code&gt; per &lt;code&gt;user&lt;/code&gt; that you want to use.&lt;/p&gt;

&lt;h2&gt;Building the SQL query&lt;/h2&gt;

&lt;p&gt;We need to select from &lt;code&gt;users&lt;/code&gt; and build a lateral join to a subquery that will select from &lt;code&gt;posts&lt;/code&gt; but filtering the &lt;code&gt;posts&lt;/code&gt; where the &lt;code&gt;posts.user_id&lt;/code&gt; is the &lt;code&gt;users.id&lt;/code&gt; and then order those &lt;code&gt;posts&lt;/code&gt; to be able to limit the selection to just the 3 &lt;code&gt;posts&lt;/code&gt; that we want to fetch.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;selected_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="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&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="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="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will return as &lt;code&gt;selected_posts&lt;/code&gt; a list with just the last three &lt;code&gt;posts&lt;/code&gt; for each &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Lateral join with rails with a fixed n&lt;/h2&gt;

&lt;p&gt;Now we need to pass this query to rails in a way that it could build &lt;code&gt;Post&lt;/code&gt; objects from this information.&lt;/p&gt;

&lt;p&gt;This is how I made it work.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      JOIN LATERAL (
        SELECT * FROM posts
        WHERE user_id = users.id
        ORDER BY id DESC LIMIT 3
      ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;p&gt;We start the query from the &lt;code&gt;User&lt;/code&gt; model and then select the &lt;code&gt;selected_posts&lt;/code&gt;. Then comes the lateral join.&lt;/p&gt;

&lt;p&gt;Then we use &lt;code&gt;Post.from&lt;/code&gt; to tell rails to evaluate the sql as a subquery and with it build the &lt;code&gt;Post&lt;/code&gt; objects.&lt;/p&gt;

&lt;p&gt;At the end it builds this sql:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;selected_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;"users"&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&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="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="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Lateral join with rails with a variable n&lt;/h2&gt;

&lt;p&gt;If you want to make the &lt;code&gt;n&lt;/code&gt; variable, you can use the sanitize the sql like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
      JOIN LATERAL (
        SELECT * FROM posts
        WHERE user_id = users.id
        ORDER BY id DESC LIMIT :limit
      ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;h2&gt;Put it in a has_many association&lt;/h2&gt;

&lt;p&gt;Now that you have build the scope, you can build a has many association passing that scope, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you will be able to preload just the &lt;code&gt;last_posts&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;5&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="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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_posts&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Code to run the example&lt;/h2&gt;

&lt;p&gt;Here is the full code that you can use to run with the &lt;a href="/active-record-playground-runner-introduction.html"&gt;Active Record Playground Runner&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;add_index&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="ss"&gt;:user_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_list_for_each_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&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="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&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="n"&gt;models&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"SQL query"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
    SELECT selected_posts.* FROM users JOIN LATERAL (
      SELECT * FROM posts
      WHERE user_id = users.id
      ORDER BY id DESC LIMIT 3
    ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;  SQL&lt;/span&gt;

  &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&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;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"Lateral join with rails with a fixed n"&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM posts
          WHERE user_id = users.id
          ORDER BY id DESC LIMIT 3
        ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;span class="n"&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;last_n_per_user&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="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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="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;:id&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;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"Lateral join with rails with a variable n"&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM posts
          WHERE user_id = users.id
          ORDER BY id DESC LIMIT :limit
        ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;span class="n"&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;last_n_per_user&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="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="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&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="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;:id&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;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"In a has many association"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;last_n_per_user&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Post"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_n_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM posts
          WHERE user_id = users.id
          ORDER BY id DESC LIMIT :limit
        ) AS selected_posts ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

      &lt;span class="n"&gt;selected_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"selected_posts.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize_sql&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

      &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"posts"&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;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:last_posts&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last_posts&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;:id&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;p&gt;You can find the code also on: &lt;a href="https://github.com/bhserna/last_n_per_user_lateral_join"&gt;github.com/bhserna/last_n_per_user_lateral_join&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Active Record Playground Runner Introduction</title>
    <link rel="alternate" href="https://bhserna.com/active-record-playground-runner-introduction"/>
    <id>https://bhserna.com/active-record-playground-runner-introduction</id>
    <published>2022-08-08T22:53:00Z</published>
    <updated>2022-08-08T22:53:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you ever wanted to just create an active record example to someone in your team without thinking in the database setup?&lt;/p&gt;

&lt;p&gt;Or maybe send two different models designs with some examples to see the difference, but the setup what just to difficult?&lt;/p&gt;

&lt;p&gt;Or like me, create a bunch of examples to teach an Active Record concept?&lt;/p&gt;

&lt;p&gt;If you have had one of this problems, maybe this tool can help you.&lt;/p&gt;

&lt;h2&gt;A tool to run Active Record examples&lt;/h2&gt;

&lt;p&gt;I want to introduce a new tool that I am working on.&lt;/p&gt;

&lt;p&gt;It will help you play with Active Record and Postgres, without the thinking in the database setup.&lt;/p&gt;

&lt;p&gt;You will be able to declare the schema, models, seeds and examples in just one file, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paragraph&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_list_for_each_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;post_id: &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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sentence&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="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"first"&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"second"&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&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;p&gt;And the run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;bundle exec run_playground file_name.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And it will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the database with a random name&lt;/li&gt;
&lt;li&gt;Load the schema&lt;/li&gt;
&lt;li&gt;Run the seeds&lt;/li&gt;
&lt;li&gt;Run the examples with a nice format and with the logger turned on.&lt;/li&gt;
&lt;li&gt;Drop the database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And print an output like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Setup
-----
-- create_table(:posts)
   -&amp;gt; 0.3001s
-- create_table(:comments)
   -&amp;gt; 0.0092s
-- add_index(:comments, :post_id)
   -&amp;gt; 0.0041s


Example: first
--------------
D, [2022-08-08T18:13:46.118881 #89680] DEBUG -- :   Post Load (4.5ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]
D, [2022-08-08T18:13:46.143351 #89680] DEBUG -- :   Comment Load (1.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5)  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5]]
D, [2022-08-08T18:13:46.156379 #89680] DEBUG -- :   Comment Count (3.8ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]
20
D, [2022-08-08T18:13:46.158504 #89680] DEBUG -- :   Comment Count (1.0ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 2]]
20
D, [2022-08-08T18:13:46.161012 #89680] DEBUG -- :   Comment Count (1.3ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 3]]
20
D, [2022-08-08T18:13:46.163467 #89680] DEBUG -- :   Comment Count (0.5ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 4]]
20
D, [2022-08-08T18:13:46.165299 #89680] DEBUG -- :   Comment Count (1.0ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 5]]
20


Example: second
---------------
D, [2022-08-08T18:13:46.166206 #89680] DEBUG -- :   Post Load (0.3ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]
D, [2022-08-08T18:13:46.167795 #89680] DEBUG -- :   Comment Load (0.7ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5)  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5]]
20
20
20
20
20
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Why this tool?&lt;/h2&gt;

&lt;p&gt;I prepared a lot of examples for the ebook &lt;a href="/avoid-n-plus-1-queries-on-rails.html"&gt;Avoid n+1 queries on rails&lt;/a&gt; using the first version of my &lt;a href="https://github.com/bhserna/active_record_playground"&gt;Active Record Playground&lt;/a&gt;. A template to help in the setup of an environment with Active Record.&lt;/p&gt;

&lt;p&gt;With the old template it was is hard to write a lot of examples with the same database schema, and it was hard to setup a project for each schema.&lt;/p&gt;

&lt;p&gt;With this tool is much more easier to create examples with different schemas because you don&amp;rsquo;t need to setup a whole project directory for each schema.&lt;/p&gt;

&lt;p&gt;It makes it easier to create more and more diverse examples. And also helping the people that see the examples to focus just on the example code instead of the project setup.&lt;/p&gt;

&lt;h2&gt;The code repository&lt;/h2&gt;

&lt;p&gt;You can find the code on: &lt;a href="https://github.com/bhserna/active_record_playground_runner"&gt;https://github.com/bhserna/active_record_playground_runner&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Installation&lt;/h2&gt;

&lt;p&gt;For now, the gem is only on github, and if you want to install it you will need to add this to your Gemfile:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;gem 'active_record_playground_runner', "~&amp;gt; 0.1.0", github: "bhserna/active_record_playground_runner", branch: "main"
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;How to use it&lt;/h2&gt;

&lt;h3&gt;Schema&lt;/h3&gt;

&lt;p&gt;In the schema you will be able to define a schema using the methods that rails uses to define the schema in a regular rails application.&lt;/p&gt;

&lt;h3&gt;Models&lt;/h3&gt;

&lt;p&gt;Here you can define the records or plain old ruby objects that you will need on the seeds.&lt;/p&gt;

&lt;h3&gt;Seeds&lt;/h3&gt;

&lt;p&gt;You can use your models to create the state you need for your examples.&lt;/p&gt;

&lt;p&gt;You will be able to use the FFaker gem. It will be already required.&lt;/p&gt;

&lt;h3&gt;Seeds Helpers&lt;/h3&gt;

&lt;p&gt;In the seeds from the example, there are two methods that are part of the gem, &lt;code&gt;create_list&lt;/code&gt; and &lt;code&gt;create_list_for_each_record&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paragraph&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_list_for_each_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;post_id: &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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sentence&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;create_list&lt;/code&gt; receives a record class, a &lt;code&gt;count&lt;/code&gt; keyword and a block. The block will be executed to create each record the number of times specified by &lt;code&gt;count&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create_list_for_each_record&lt;/code&gt; also expects a list of &lt;code&gt;records&lt;/code&gt; and for each record it will run the block &lt;code&gt;count&lt;/code&gt; times.&lt;/p&gt;

&lt;p&gt;Both methods will first build data as an array of hashes, and then use &lt;code&gt;insert_all&lt;/code&gt; to create the records. Be aware that if you use this methods the callbacks will not be fired.&lt;/p&gt;

&lt;h3&gt;Examples&lt;/h3&gt;

&lt;p&gt;You can define as many examples as you want, you can pass a name if you need it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"My example"&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&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;p&gt;If you need to define changes to the model classes, you will be able to do it inside the example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"third"&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
    &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
  &lt;span class="k"&gt;end&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:latest_comment&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&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;h2&gt;Run a standalone playground&lt;/h2&gt;

&lt;p&gt;If you want to run a playground without the need to install the gem apart or with a Gemfile, you can use bundler/inline and run the playground like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'bundler/inline'&lt;/span&gt;

&lt;span class="n"&gt;gemfile&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'active_record_playground_runner'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;github: &lt;/span&gt;&lt;span class="s2"&gt;"bhserna/active_record_playground_runner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;branch: &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"active_record_playground_runner"&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecordPlaygroundRunner&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Playground&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="s2"&gt;"My playground name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="k"&gt;do&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
      &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
      &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paragraph&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_list_for_each_record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;records: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;post_id: &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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="no"&gt;FFaker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CheesyLingo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sentence&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="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"first"&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"second"&lt;/span&gt; &lt;span class="k"&gt;do&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;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then run the file like any other ruby file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby my_playground.rb
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Mistakes we make with counts in rails</title>
    <link rel="alternate" href="https://bhserna.com/mistakes-we-make-with-counts-in-rails"/>
    <id>https://bhserna.com/mistakes-we-make-with-counts-in-rails</id>
    <published>2022-06-06T22:51:00Z</published>
    <updated>2022-06-06T22:51:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Display the count of an association could look like a simple task, but in some cases it can give you real troubles.&lt;/p&gt;

&lt;p&gt;It is very easy to end with n+1 queries. Counting with ruby sometimes can be ok, but sometimes could be impossible. Counting via sql is usually ok, but sometimes could be slow or maybe unnecessary.&lt;/p&gt;

&lt;p&gt;Here I will share five mistakes with counts I have made and seen. It could help you to at least be aware of them.&lt;/p&gt;

&lt;h2&gt;1. Using slow sql counts in views&lt;/h2&gt;

&lt;p&gt;Sometimes counting via SQL using &lt;code&gt;count&lt;/code&gt; or &lt;code&gt;size&lt;/code&gt; to get a single value or by &lt;a href="/preload-counts-active-record.html"&gt;preloading counts&lt;/a&gt; can be slow depending of the number of records you are counting.&lt;/p&gt;

&lt;p&gt;For example, imagine you need to put the number of likes in a post and you could have posts with thousands of likes. It could make your page slow.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Somtimes an SQL count can make your&lt;/span&gt;
&lt;span class="c1"&gt;# page slow when the count is to large&lt;/span&gt;
&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="c1"&gt;#...&lt;/span&gt;

&lt;span class="c1"&gt;# Sometimes one count is ok but&lt;/span&gt;
&lt;span class="c1"&gt;# preloading a group of counts can be slow&lt;/span&gt;
&lt;span class="n"&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;limit&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;span class="n"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Like&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

&lt;span class="n"&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="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In those cases you can try a &lt;a href="/solve-nplus1-queries-and-slow-counts-with-counter-caches.html"&gt;counter cache&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# You can use a counter cache&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Like&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :true&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="c1"&gt;#... No query here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or maybe &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching"&gt;fragment caching&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;p&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;abstract&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Likes: &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'posts/post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cached: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;2. Count via sql an association you will use later&lt;/h2&gt;

&lt;p&gt;Sometimes in a page we show a count calculated via SQL using &lt;code&gt;count&lt;/code&gt; or &lt;code&gt;size&lt;/code&gt;, and later in the page we load the full collection we have just counted to display the information.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;span class="c1"&gt;# Count post via sql (1 query)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

&lt;span class="c1"&gt;# Load posts (other query)&lt;/span&gt;
&lt;span class="n"&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="nb"&gt;puts&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="nb"&gt;puts&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;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;p&gt;In cases like this, you can load and count the collection with &lt;code&gt;length&lt;/code&gt; or &lt;code&gt;load.size&lt;/code&gt; to avoid making two calls to the database:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;span class="c1"&gt;# Load posts and count with ruby (1 query)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;

&lt;span class="c1"&gt;# Posts already loaded (no more queries)&lt;/span&gt;
&lt;span class="n"&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="nb"&gt;puts&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="nb"&gt;puts&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;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;3. N+1 queries for counting associations inside a list&lt;/h2&gt;

&lt;p&gt;I think that this is the most common problem, asking the database for a count inside an each loop.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;span class="c1"&gt;# Load posts (1 query)&lt;/span&gt;
&lt;span class="n"&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="c1"&gt;# Count comments via sql (1 query per post)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To solve it you can try one of these three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To &lt;a href="/preload-counts-active-record.html"&gt;preload the counts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;To use a &lt;a href="/solve-nplus1-queries-and-slow-counts-with-counter-caches.html"&gt;counter cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;To use &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching"&gt;fragment caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;4. Preload associations for counting inside a list&lt;/h2&gt;

&lt;p&gt;When you need to put a count inside an each loop and avoid n+1 sometimes we first preload the association and then count the elements in ruby.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Load posts and comments (2 queries)&lt;/span&gt;
&lt;span class="n"&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="c1"&gt;# Count with ruby (no queries)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This could be good enough in a lot of cases, but if the association have too many records and specially if you are not paginating the list, it could cause some performance and memory problems. &lt;/p&gt;

&lt;p&gt;To solve it you can also try one of these three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To &lt;a href="/preload-counts-active-record.html"&gt;preload the counts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;To use a &lt;a href="/solve-nplus1-queries-and-slow-counts-with-counter-caches.html"&gt;counter cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;To use &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html#fragment-caching"&gt;fragment caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;5. Forget fragment caching&lt;/h2&gt;

&lt;p&gt;If you can enable caching in your project you can also try to avoid the n+1 queries or slow counts in the list with fragment caching.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://www.youtube.com/watch?v=ktZLpjCanvg"&gt;think of n+1 as a feature&lt;/a&gt;, let the n+1 queries run just the first time and use the cache after that.&lt;/p&gt;

&lt;p&gt;For example if you have a &lt;code&gt;posts/_post&lt;/code&gt; partial like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;p&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;abstract&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Likes: &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then in the index view call the partial with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'posts/post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cached: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will have n+1 queries on the first render, but after that rails will read the cache.&lt;/p&gt;

&lt;p&gt;You can go to the &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html"&gt;Caching with Rails: An Overview&lt;/a&gt; to learn more about rails caching.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A tool and examples to help you implement the first or last per record in rails with one query</title>
    <link rel="alternate" href="https://bhserna.com/tool-to-implement-first-or-last-per-record-rails"/>
    <id>https://bhserna.com/tool-to-implement-first-or-last-per-record-rails</id>
    <published>2022-05-31T11:58:00Z</published>
    <updated>2022-05-31T11:58:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you ever needed to get the most recent post for each user in rails, but didn&amp;rsquo;t know how to do it without using map?&lt;/p&gt;

&lt;p&gt;Or maybe something similar, like:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The first or last comment for each post&lt;/li&gt;
&lt;li&gt;The first or last payment for each customer&lt;/li&gt;
&lt;li&gt;The first or last review for each customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of a simple way to do it when you can use the &lt;code&gt;id&lt;/code&gt; column to sort the records.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;first_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"min(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;last_per_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user_id&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;span class="nb"&gt;puts&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;first_per_user&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "posts".*&lt;/span&gt;
&lt;span class="c1"&gt;#    FROM "posts"&lt;/span&gt;
&lt;span class="c1"&gt;#    WHERE "posts"."id"&lt;/span&gt;
&lt;span class="c1"&gt;#    IN (SELECT min(id) FROM "posts" GROUP BY "posts"."user_id")&lt;/span&gt;

&lt;span class="nb"&gt;puts&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;last_per_user&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "posts".*&lt;/span&gt;
&lt;span class="c1"&gt;#    FROM "posts"&lt;/span&gt;
&lt;span class="c1"&gt;#    WHERE "posts"."id"&lt;/span&gt;
&lt;span class="c1"&gt;#    IN (SELECT max(id) FROM "posts" GROUP BY "posts"."user_id")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But here you will find a repo, with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 different methods to implement the &amp;ldquo;first or last per user&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;2 benchmarks, memory and ips, that you can run to test which method is better for your use case.&lt;/li&gt;
&lt;li&gt;2 examples on how to associate the posts to the users, and do something like &lt;code&gt;user.last_post&lt;/code&gt; or &lt;code&gt;posts[user]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the repo in: &lt;a href="https://github.com/bhserna/first_or_last_per_user"&gt;bhserna/first_or_last_per_user&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The 5 methods are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/00_min_max.rb"&gt;Using min() and max()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/01_distinct_on.rb"&gt;Using distinct_on&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/02_distinct_on_arel.rb"&gt;Using distinct_on with arel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/03_lateral_join.rb"&gt;Using a lateral join&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/04_window_function.rb"&gt;Using a window function&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 2 benchamarks are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/05_memory_benchmark.rb"&gt;Memory benchmark code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/06_ips_benchmark.rb"&gt;Iterations per second benchmark code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 2 examples on how to associates the posts to the users are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/07_has_one.rb"&gt;A has one association&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user/blob/main/examples/08_preload_object.rb"&gt;A preload object&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;How to run the examples&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the dependencies with &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database setup - run the command:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby db/setup.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;Run the examples with &lt;code&gt;ruby examples/&amp;lt;file name&amp;gt;&lt;/code&gt;. For example:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby example/00_example.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;Change the seeds  on &lt;code&gt;db/seeds.rb&lt;/code&gt; and re-run &lt;code&gt;ruby db/setup.rb&lt;/code&gt; to test different scenarios.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Active Record Playground&lt;/h2&gt;

&lt;p&gt;This repo uses the &lt;a href="/active-record-playground.html"&gt;Active Record Playground&lt;/a&gt; that you can also use to play with some Active Record models in isolation and without creating a full rails app.&lt;/p&gt;

&lt;h2&gt;Inspiration&lt;/h2&gt;

&lt;p&gt;This example is based on a &lt;a href="https://twitter.com/stevepolitodsgn/status/1503345127846301703"&gt;proposal&lt;/a&gt; of &lt;a href="https://twitter.com/stevepolitodsgn"&gt;Steave Polito&lt;/a&gt;, but trying to implement the samething with one query.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Solve n+1 queries and slow counts with counter caches</title>
    <link rel="alternate" href="https://bhserna.com/solve-nplus1-queries-and-slow-counts-with-counter-caches"/>
    <id>https://bhserna.com/solve-nplus1-queries-and-slow-counts-with-counter-caches</id>
    <published>2022-05-19T11:45:00Z</published>
    <updated>2022-05-19T11:45:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Sometimes calculating the count of a collection on every page render, even in SQL, is not the best option.&lt;/p&gt;

&lt;p&gt;Imagine that you need to render a post, with its count of likes and comments, but the post has thousands of likes and comments. That would be slow.&lt;/p&gt;

&lt;p&gt;One solution is to save the count of those comments and likes in the record, and when you need to render the post, instead of counting the elements again, just display the count that you already saved.&lt;/p&gt;

&lt;p&gt;Keeping that count in sync is not easy, but rails makes it easy for us with a feature called “counter cache”.&lt;/p&gt;

&lt;p&gt;Here I will show you how you can use a counter caches, how to introduce it in an existent association and some tips to work with them.&lt;/p&gt;

&lt;h2&gt;Add a counter cache to a new association with the rails defaults&lt;/h2&gt;

&lt;p&gt;To add a counter cache you will need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;counter_cache: true&lt;/code&gt; option in the &lt;code&gt;belongs_to&lt;/code&gt; declaration.&lt;/li&gt;
&lt;li&gt;Add a column to keep the count in the associated model (the one with the &lt;code&gt;has_many&lt;/code&gt;), with the name &lt;code&gt;#{table_name}_count&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example to keep the count of likes in a post, you can add a &lt;code&gt;likes_count&lt;/code&gt; coulumn in the &lt;code&gt;posts&lt;/code&gt; table.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&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;Like&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="c1"&gt;# 1. Add the counter_cache: true option in the belongs_to declaration&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&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;CreatePosts&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="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;#...&lt;/span&gt;
      &lt;span class="c1"&gt;# 2. Add a column to keep the count in the associated model (has_many)&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:likes_count&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Custom counter cache columns names&lt;/h2&gt;

&lt;p&gt;If you want to add a different name to the counter cache column you have to specify the name of the column in both association declarations, the &lt;code&gt;belongs_to&lt;/code&gt; and the &lt;code&gt;has_many&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="c1"&gt;# Specify the custom column name in the has_many association&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :likes_total&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;Like&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="c1"&gt;# Specify the custom column name in the belongs_to association&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :likes_total&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;CreatePosts&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="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;#...&lt;/span&gt;
      &lt;span class="c1"&gt;# 2. Add a column to keep the count in the associated model (has_many)&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:likes_total&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:post&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is important to remember to also define the &lt;code&gt;counter_cache&lt;/code&gt; option in the &lt;code&gt;has_many&lt;/code&gt; association. Without it, the &lt;code&gt;size&lt;/code&gt; method won’t be able to read your counter cache column.&lt;/p&gt;

&lt;h2&gt;Reset the counters for an existent association with just a few records&lt;/h2&gt;

&lt;p&gt;If you want to add a counter cache to an existing association that already has records created, you will need to populate the counter column.&lt;/p&gt;

&lt;p&gt;If you don’t have too many records to update you can use the &lt;code&gt;reset_counters&lt;/code&gt; class method.&lt;/p&gt;

&lt;p&gt;You can iterate through each record and reset its counter.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&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;Like&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="c1"&gt;# 1. Add the counter_cache: true option in the belongs_to declaration&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&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;AddLikesCountToPosts&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="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Add a column to keep the count in the associated model (has_many)&lt;/span&gt;
    &lt;span class="n"&gt;add_column&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="ss"&gt;:likes_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Update the counters for each post&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;find_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="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_counters&lt;/span&gt;&lt;span class="p"&gt;(&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;touch: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# The touch is optional&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;remove_column&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="ss"&gt;:likes_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&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;p&gt;The &lt;code&gt;reset_counters&lt;/code&gt; method will make a database call for each &lt;code&gt;post&lt;/code&gt;, and if you have many records it will take a lot of time. Remember to just use this method if you don&amp;rsquo;t have too many records.&lt;/p&gt;

&lt;h2&gt;Reset the counters with SQL, for associations with many records&lt;/h2&gt;

&lt;p&gt;As I said before, If you will add the counter cache to an existing association that already has records, you will need to populate the counter column.&lt;/p&gt;

&lt;p&gt;But If you have too many records to update, the previous method can be really slow, because it will make a database call for each record.&lt;/p&gt;

&lt;p&gt;Other way of accomplish the task is by executing sql directly, like it is described by Ryan McGeary in the post &lt;a href="http://ryan.mcgeary.org/2016/02/05/proper-counter-cache-migrations-in-rails/"&gt;Proper Counter Cache Migrations in Rails&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&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;Like&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="c1"&gt;# 1. Add the counter_cache: true option in the belongs_to declaration&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&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;AddLikesCountToPosts&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="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Add a column to keep the count in the associated model (has_many)&lt;/span&gt;
    &lt;span class="n"&gt;add_column&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="ss"&gt;:likes_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Update the counters for each post&lt;/span&gt;
    &lt;span class="n"&gt;update_counters&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;
    &lt;span class="n"&gt;remove_column&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="ss"&gt;:likes_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_counters&lt;/span&gt; 
    &lt;span class="n"&gt;execute&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;squish&lt;/span&gt;&lt;span class="sh"&gt;
      UPDATE posts
      SET likes_count = (
        SELECT count(1)
        FROM likes
        WHERE likes.post_id = posts.id
      )
&lt;/span&gt;&lt;span class="no"&gt;    SQL&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;h2&gt;How to use the counter cache in your views?&lt;/h2&gt;

&lt;p&gt;You can use it in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using the method with the column name&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;size&lt;/code&gt; method&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&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;Like&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&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="c1"&gt;# Using the method with the column name&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;likes_count&lt;/span&gt;

  &lt;span class="c1"&gt;# "size" will also read you counter cache column&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;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you are using a custom column name, you can also use both, the column name and the size method.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :total_likes&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;Like&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :total_likes&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&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="c1"&gt;# Using the method with the column name&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;total_likes&lt;/span&gt;

  &lt;span class="c1"&gt;# "size" will also read you counter cache column&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;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But, when you are using a custom counter cache name, is important to remember to also define the &lt;code&gt;counter_cache&lt;/code&gt; option in the &lt;code&gt;has_many&lt;/code&gt; association. Without it &lt;code&gt;size&lt;/code&gt; won’t be able to read your counter cache column.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="c1"&gt;# If you don't define the custom counter cache column...&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;likes&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;Like&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :total_likes&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&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="c1"&gt;# "size" won't know the custom column name and will execute a COUNT query&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;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://guides.rubyonrails.org/association_basics.html]"&gt;Rails Guides - Active Record Associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html"&gt;Rails API - ActiveRecord::CounterCache::ClassMethods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ryan.mcgeary.org/2016/02/05/proper-counter-cache-migrations-in-rails/"&gt;Proper Counter Cache Migrations in Rails&lt;/a&gt; by Ryan McGeary.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>4 things you can try before using a counter cache in rails</title>
    <link rel="alternate" href="https://bhserna.com/what-can-you-try-before-using-a-counter-cache-in-rails"/>
    <id>https://bhserna.com/what-can-you-try-before-using-a-counter-cache-in-rails</id>
    <published>2022-05-16T22:18:00Z</published>
    <updated>2022-05-16T22:18:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe you have heard about the counter cache feature.&lt;/p&gt;

&lt;p&gt;A counter cache makes finding the number of belonging objects more efficient by keeping a column with the count.&lt;/p&gt;

&lt;p&gt;Rails makes it easy to implement it, but is not free. Sometimes it may be better not to use it.&lt;/p&gt;

&lt;p&gt;Here are four things you could try before or instead of using a counter cache, without introuducing n+1 queries.&lt;/p&gt;

&lt;h2&gt;1. If you don’t need the count in a list just count the records&lt;/h2&gt;

&lt;p&gt;If you don’t need to use the count inside a list, and instead you just want to show the count of the association in a details or stats page, you can try to count the records via SQL using &lt;code&gt;count&lt;/code&gt; or &lt;code&gt;size&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.4ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;

&lt;span class="c1"&gt;# or...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.4ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you need to load all the records in the association in the same page and without pagination, you can load the records and count the records with ruby using &lt;code&gt;length&lt;/code&gt; or &lt;code&gt;load.size&lt;/code&gt;, to make just one call to the database instead of two.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&lt;/span&gt;

&lt;span class="c1"&gt;# Length will load the coments and count the elements in ruby&lt;/span&gt;
&lt;span class="c1"&gt;# avoiding one extra database call.&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;

&lt;span class="c1"&gt;# or...&lt;/span&gt;
&lt;span class="c1"&gt;# #load will load the coments and #size will count the elements&lt;/span&gt;
&lt;span class="c1"&gt;# in ruby avoiding one extra database call.&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you have doubt when is better to use count vs size vs length you can read the post:
&lt;a href="/count-size-length-active-record.html"&gt;Difference between count, length and size in an association with ActiveRecord&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;2. Preload the records in a list&lt;/h2&gt;

&lt;p&gt;If you need to display the count of an association for each record in a list, and you know the records &lt;strong&gt;wont&amp;rsquo;t have too many associated records&lt;/strong&gt;, you can try to preload the records and use &lt;code&gt;lenght&lt;/code&gt; or &lt;code&gt;size&lt;/code&gt; to count the records in ruby.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Preload the full association&lt;/span&gt;
&lt;span class="n"&gt;posts&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;:comments&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="c1"&gt;# Use size to count with ruby&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;

  &lt;span class="c1"&gt;# or lenght&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But be carefull, because if the association has too many records, you can &lt;a href="/fixing-n-plus-1-queries-can-hurt-performance.html"&gt;introduce a performance problem&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;3. Preload the counts you’ll need in a list&lt;/h2&gt;

&lt;p&gt;If you need to display the count of an association for each record in a list, and you know the records &lt;strong&gt;will have too many associated records&lt;/strong&gt;, as I said before, preloading all the associated records can &lt;a href="/fixing-n-plus-1-queries-can-hurt-performance.html"&gt;introduce a performance problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead you can try to preload just the counts with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&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;span class="c1"&gt;# Filter the likes for those posts and group them by count&lt;/span&gt;
&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Like&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

&lt;span class="n"&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="c1"&gt;# Find the likes count for the current post&lt;/span&gt;
  &lt;span class="c1"&gt;# and fallback to zero if no count&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to see other ways to preload the counts, you can see the post: &lt;a href="/preload-counts-active-record.html"&gt;How to preload counts in a list with ActiveRecord&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;4. Fragment caching&lt;/h2&gt;

&lt;p&gt;If you can enable caching in your project you can also try to avoid the n+1 queries in the list with fragment caching.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://www.youtube.com/watch?v=ktZLpjCanvg"&gt;think of n+1 as a feature&lt;/a&gt;, and let the n+1 queries run just the first time and use the cache after that.&lt;/p&gt;

&lt;p&gt;For example if you have a &lt;code&gt;posts/_post&lt;/code&gt; partial like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;&amp;lt;p&amp;gt;&amp;lt;%= post.abstract %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;span&amp;gt;Likes: &amp;lt;%= posts.likes.size %&amp;gt;&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then in the index view, you call the partial with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;&amp;lt;%= render partial: 'posts/post', collection: posts, cached: true %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will have n+1 queries on the first render, but after that, rails will read the cache instead avoiding the n+1 queries.&lt;/p&gt;

&lt;p&gt;To learn more about rails caching you can go to: &lt;a href="https://guides.rubyonrails.org/caching_with_rails.html"&gt;Caching with Rails: An Overview&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Warning: Don’t try too hard to avoid the counter cache&lt;/h2&gt;

&lt;p&gt;Although sometimes you can avoid the counter cache with these tactics, sometimes these tactics can also give you some troubles.&lt;/p&gt;

&lt;p&gt;So take this ideas as suggestions, but don’t think that one of this will solve all your problems every time.&lt;/p&gt;

&lt;p&gt;Take them as options to help you solve your current problem.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>When do you need a counter cache in rails?</title>
    <link rel="alternate" href="https://bhserna.com/when-do-you-need-a-counter-cache"/>
    <id>https://bhserna.com/when-do-you-need-a-counter-cache</id>
    <published>2022-05-09T22:12:00Z</published>
    <updated>2022-05-09T22:12:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe you have heard about the &lt;a href="https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache"&gt;counter cache&lt;/a&gt; feature.&lt;/p&gt;

&lt;p&gt;It makes finding the number of belonging objects more efficient, by keeping a column with the count.&lt;/p&gt;

&lt;p&gt;But&amp;hellip; When should you use it? Do you need to have a counter cache for every association count in your app?&lt;/p&gt;

&lt;p&gt;Here are three situations when you could need a counter cache.&lt;/p&gt;

&lt;h2&gt;When you have slow counts in your views&lt;/h2&gt;

&lt;p&gt;Even if you do the &lt;a href="count-size-length-active-record.html"&gt;count via SQL&lt;/a&gt;, a single count can be slow enough to need a counter cache.&lt;/p&gt;

&lt;p&gt;Imagine you need to put the number of likes in a post.&lt;/p&gt;

&lt;p&gt;If a post could have thousands or millions of likes, a single count, can really hit the performance of the app.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# If there are thousands or millions of likes&lt;/span&gt;
&lt;span class="c1"&gt;# counting via SQL could also be slow&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case you could try a counter cache or maybe fragment caching.&lt;/p&gt;

&lt;h2&gt;When preloading the counts in a list is slow&lt;/h2&gt;

&lt;p&gt;Sometimes preloading counts for a list via SQL is not fast enough.&lt;/p&gt;

&lt;p&gt;Imagine you need to put the number of likes for each post in a list.&lt;/p&gt;

&lt;p&gt;If there could be posts with thousands or millions of likes, the count for a list of posts could be really slow.&lt;/p&gt;

&lt;p&gt;Even if you are already just &lt;a href="preload-counts-active-record.html"&gt;preloading the counts&lt;/a&gt; to avoid n+1 queries with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&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;span class="c1"&gt;# If there are thousands or millions of likes per post&lt;/span&gt;
&lt;span class="c1"&gt;# preloading the counts could also be slow&lt;/span&gt;
&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Like&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

&lt;span class="n"&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="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case you could also try a counter cache or maybe fragment caching.&lt;/p&gt;

&lt;h2&gt;When you use a lot a count in the application&lt;/h2&gt;

&lt;p&gt;Sometimes you will be using the count in a lot of places in the app.&lt;/p&gt;

&lt;p&gt;You will need to preload the count in every place, raising the possibility to introduce n+1 queries.&lt;/p&gt;

&lt;p&gt;In this situation it could be a good option to implement a counter cache, even if you already have a preload object.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Use dom_id helper on capybara with rspec and rails</title>
    <link rel="alternate" href="https://bhserna.com/rails-dom_id-helper-capybara-rspec"/>
    <id>https://bhserna.com/rails-dom_id-helper-capybara-rspec</id>
    <published>2022-05-02T22:14:00Z</published>
    <updated>2022-05-02T22:14:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;At least for me it is very common to use the &lt;code&gt;within&lt;/code&gt; method in capybara, to match the &lt;code&gt;dom_id&lt;/code&gt; output.&lt;/p&gt;

&lt;p&gt;And is very common to do it with code like &lt;code&gt;&amp;quot;#user_#{user.id}&amp;quot;&lt;/code&gt; or &lt;code&gt;#project_#{project.id}&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;In this way:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#user_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# or...&lt;/span&gt;

&lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#project_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you do it one time, there is no trouble, but if you start doing it in every file, maybe you will want to do something to remove the repetition.&lt;/p&gt;

&lt;p&gt;Here I will show you two tips, to help you avoid this repetition.&lt;/p&gt;

&lt;h2&gt;Tip 1: Include ActionView::RecordIdentifier module to use the dom_id on your specs&lt;/h2&gt;

&lt;p&gt;On your spec you can include &lt;code&gt;ActionView::RecordIdentifier&lt;/code&gt; and use the &lt;code&gt;dom_id&lt;/code&gt; helper in your spec/scenario.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"My spec"&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;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordIdentifier&lt;/span&gt;

  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;p&gt;You can do it because the module &lt;a href="https://api.rubyonrails.org/classes/ActionView/RecordIdentifier.html"&gt;ActionView::RecordIdentifier&lt;/a&gt; defines the method &lt;code&gt;dom_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to use you the helper in all your specs you can include the module inside you rspec configuration block like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you will be able to use the helper without including the module on each spec file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"My spec"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="s2"&gt;"#&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Tip 2: Create a css_id helper to also avoid the interpolation&lt;/h2&gt;

&lt;p&gt;If you don’t want to repeat yourself doing &lt;code&gt;&amp;quot;##{dom_id(user)}&amp;quot;&lt;/code&gt; all over the place, you can write a method &lt;code&gt;css_id&lt;/code&gt; (or whatever you want to call it) to do it for you.&lt;/p&gt;

&lt;p&gt;You can add a new file  &lt;code&gt;spec/support/record_identifier_helper.rb&lt;/code&gt; and write something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RecordIdentifierHelper&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordIdentifier&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;css_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s2"&gt;"#"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then you need to be sure that in your &lt;code&gt;rails_helper&lt;/code&gt; you are loading that file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Normally you will have something like this.&lt;/span&gt;
&lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"system/support/**/*.rb"&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then include your module on your rspec configuration block:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt; &lt;span class="no"&gt;RecordIdentifierHelper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :system&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you will be able to use the helper on your specs, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"My spec"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="n"&gt;css_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;within&lt;/span&gt; &lt;span class="n"&gt;css_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>How to preload counts in a list with ActiveRecord</title>
    <link rel="alternate" href="https://bhserna.com/preload-counts-active-record"/>
    <id>https://bhserna.com/preload-counts-active-record</id>
    <published>2022-04-25T22:19:00Z</published>
    <updated>2022-04-25T22:19:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you need to put the number of likes for each post in a list, but avoiding n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="c1"&gt;# n+1 queries&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One way to avoid n+1 queries here, is to preload the association and the count the records in ruby. But if the association has too many records, you could &lt;a href="/fixing-n-plus-1-queries-can-hurt-performance.html"&gt;introduce a performance problem&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:likes&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To solve it you can try to preload just the counts, here are four ways of doing it (and a bonus).&lt;/p&gt;

&lt;h3&gt;Method 1: Count from the associated model&lt;/h3&gt;

&lt;p&gt;You will need to&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filter the associated models for the records on your list&lt;/li&gt;
&lt;li&gt;Group and count by the foreign_key to obtain a hash with the form { id ⇒ count }&lt;/li&gt;
&lt;li&gt;Use that hash on the list to get the count for each record&lt;/li&gt;
&lt;li&gt;Fallback to zero if no count&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here the example with posts and likes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Given a list of posts&lt;/span&gt;
&lt;span class="n"&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;limit&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;span class="c1"&gt;# 1. Filter the likes for those posts&lt;/span&gt;
&lt;span class="n"&gt;likes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Like&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Group by :post_id and count&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;

&lt;span class="n"&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="c1"&gt;# 3. Find the likes count for the current post&lt;/span&gt;
  &lt;span class="c1"&gt;# 4. Fallback to zero if no count&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Like Count (0.8ms)  SELECT COUNT(*) AS "count_all", "likes"."post_id" AS "likes_post_id" FROM "likes" WHERE "likes"."post_id" IN (SELECT "posts"."id" FROM "posts" LIMIT $1) GROUP BY "likes"."post_id"  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# {3=&amp;gt;20, 2=&amp;gt;20, 4=&amp;gt;20}&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load (0.2ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 1, likes: 0&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 2, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 3, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 4, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 5, likes: 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Method 2: Count joining the associated model&lt;/h3&gt;

&lt;p&gt;You will need to&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Join the association&lt;/li&gt;
&lt;li&gt;Group by the id of the base table and count the ids of the associated records to obtain a hash with the form { id ⇒ count }&lt;/li&gt;
&lt;li&gt;Use that hash on the list to get the count for each record&lt;/li&gt;
&lt;li&gt;Fallback to zero if no count&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here the example with posts and likes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Given a list of posts&lt;/span&gt;
&lt;span class="n"&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;limit&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;span class="c1"&gt;# 1. Join the likes&lt;/span&gt;
&lt;span class="c1"&gt;# 2. Group by "posts.id" and count "likes.id"&lt;/span&gt;
&lt;span class="n"&gt;counts&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="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts.id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"likes.id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;

&lt;span class="n"&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="c1"&gt;# 3. Find the likes count for the current post&lt;/span&gt;
  &lt;span class="c1"&gt;# 4. Fallback to zero if no count&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Post Count (1.0ms)  SELECT COUNT("likes"."id") AS "count_likes_id", "posts"."id" AS "posts_id" FROM "posts" INNER JOIN "likes" ON "likes"."post_id" = "posts"."id" GROUP BY "posts"."id" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# {2=&amp;gt;20, 3=&amp;gt;20, 4=&amp;gt;20, 8=&amp;gt;20}&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load (0.3ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 1, likes: 0&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 2, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 3, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 4, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 5, likes: 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Method 3: Count left joining the associated model&lt;/h3&gt;

&lt;p&gt;You will need to&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Left join the association&lt;/li&gt;
&lt;li&gt;Group by the id of the base table and count the ids of the associated records to obtain a hash with the form { id ⇒ count }&lt;/li&gt;
&lt;li&gt;Use that hash on the list to get the count for each record&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don’t need a fallback to zero, because the left join will return a count for all posts.&lt;/p&gt;

&lt;p&gt;Here the example with posts and likes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Given a list of posts&lt;/span&gt;
&lt;span class="n"&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;limit&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;span class="c1"&gt;# 1. Left join the likes&lt;/span&gt;
&lt;span class="c1"&gt;# 2. Group by "posts.id" and count "likes.id"&lt;/span&gt;
&lt;span class="n"&gt;counts&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="nf"&gt;left_joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts.id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"likes.id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;

&lt;span class="n"&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="c1"&gt;# 3. Find the likes count for the current post&lt;/span&gt;
  &lt;span class="c1"&gt;# You don't need fallback&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Post Count (0.4ms)  SELECT COUNT("likes"."id") AS "count_likes_id", "posts"."id" AS "posts_id" FROM "posts" LEFT OUTER JOIN "likes" ON "likes"."post_id" = "posts"."id" GROUP BY "posts"."id" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# {1=&amp;gt;0, 2=&amp;gt;20, 3=&amp;gt;20, 4=&amp;gt;20, 5=&amp;gt;0}&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load (0.2ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 1, likes: 0&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 2, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 3, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 4, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 5, likes: 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Method 4: Add the count to the selected values&lt;/h3&gt;

&lt;p&gt;You will need to&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Left join the association&lt;/li&gt;
&lt;li&gt;Select the records table and the associations count&lt;/li&gt;
&lt;li&gt;Group by the id of the base table&lt;/li&gt;
&lt;li&gt;Use the associations count as a method in the records (Ej. &lt;code&gt;likes_count&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don’t need a fallback to zero, because the left join will return a count for all posts.&lt;/p&gt;

&lt;p&gt;Here the example with posts and likes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Given a list of posts&lt;/span&gt;
&lt;span class="n"&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;limit&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;span class="c1"&gt;# 1. Join the likes&lt;/span&gt;
&lt;span class="c1"&gt;# 2. Select the posts and the likes count&lt;/span&gt;
&lt;span class="c1"&gt;# 3. Group by posts.id&lt;/span&gt;
&lt;span class="n"&gt;posts&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="nf"&gt;left_joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts.*, COUNT(likes.id) AS likes_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"posts.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="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="c1"&gt;# 4. Use post.likes_count&lt;/span&gt;
  &lt;span class="c1"&gt;# You don't need fallback&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&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;likes_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Post Load (1.0ms)  SELECT posts.*, COUNT(likes.id) AS likes_count FROM "posts" LEFT OUTER JOIN "likes" ON "likes"."post_id" = "posts"."id" GROUP BY "posts"."id" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 1, likes: 0&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 2, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 3, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 4, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 5, likes: 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Bonus: Use a preload object with one of the first three methods&lt;/h3&gt;

&lt;p&gt;If you don’t want to have this code in the view or controller, you can write a &lt;a href="/preload-object.html"&gt;preload object&lt;/a&gt; to do it. It could be something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LikesCounts&lt;/span&gt;
  &lt;span class="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 3. Find the likes count for the current post&lt;/span&gt;
    &lt;span class="c1"&gt;# 4. Fallback to zero if no count&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;counts&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Filter the likes for those posts&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Group by :post_id and count&lt;/span&gt;
    &lt;span class="vi"&gt;@counts&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Like&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;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;limit&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;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LikesCounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post: &lt;/span&gt;&lt;span class="si"&gt;#{&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;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, likes: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Post Load (0.7ms)  SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Like Count (0.4ms)  SELECT COUNT(*) AS "count_all", "likes"."post_id" AS "likes_post_id" FROM "likes" WHERE "likes"."post_id" IN (SELECT "posts"."id" FROM "posts" LIMIT $1) GROUP BY "likes"."post_id"  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 1, likes: 0&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 2, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 3, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 4, likes: 20&lt;/span&gt;
&lt;span class="c1"&gt;# Post: 5, likes: 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Difference between count, length and size in an association with ActiveRecord</title>
    <link rel="alternate" href="https://bhserna.com/count-size-length-active-record"/>
    <id>https://bhserna.com/count-size-length-active-record</id>
    <published>2022-04-18T22:11:00Z</published>
    <updated>2022-04-18T22:11:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;With Rails/ActiveRecord, you can count the records in an association using three methods &lt;code&gt;.count&lt;/code&gt;, &lt;code&gt;.length&lt;/code&gt;, &lt;code&gt;.size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a rails newbie, when you first discover this, it can be confusing&amp;hellip; Why do we need three methods? Is there any advantage to using one method over the other? How are they different?&lt;/p&gt;

&lt;p&gt;Here are 6 characteristics of this three methods that you should know. They will help you decide which method to use on different situations.&lt;/p&gt;

&lt;p&gt;I will also share a why I think that most of the time you should start with &lt;code&gt;.size&lt;/code&gt; and the reference to the docs where you can find more information.&lt;/p&gt;

&lt;h2&gt;1. &amp;ldquo;count&amp;rdquo; always performs an SQL &lt;code&gt;COUNT&lt;/code&gt; query&lt;/h2&gt;

&lt;p&gt;Even if you call it twice.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.6ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.3ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Even if the collection has already been loaded it will always execute a &lt;code&gt;COUNT&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.5ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;2. &amp;ldquo;length&amp;rdquo; loads the records and calculates the length with ruby&lt;/h2&gt;

&lt;p&gt;If you later need to use the records, they will be already loaded.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# (no query)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;3. &amp;ldquo;length&amp;rdquo; if loaded, only calculates the length with ruby&lt;/h2&gt;

&lt;p&gt;If the collection is already loaded, it won’t need a new database call.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;
&lt;span class="c1"&gt;# (no query)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;4. &amp;ldquo;size&amp;rdquo; if loaded, calculates the length with ruby&lt;/h2&gt;

&lt;p&gt;Like with &lt;code&gt;length&lt;/code&gt;, if the collection is already loaded it will compute the length with ruby.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# (no query)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;5. &amp;ldquo;size&amp;rdquo; if not loaded, performs an SQL &lt;code&gt;COUNT&lt;/code&gt; query&lt;/h2&gt;

&lt;p&gt;Like &lt;code&gt;count&lt;/code&gt;, it will execute a &lt;code&gt;COUNT&lt;/code&gt; if the collection has not been loaded.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.4ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And also like &lt;code&gt;count&lt;/code&gt;, if you use it twice it will make two queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (1.5ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# Comment Count (0.7ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1  [["post_id", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;6. &amp;ldquo;size&amp;rdquo; if there is a counter cache, uses the cached count&lt;/h2&gt;

&lt;p&gt;It will never need a database call.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&lt;/span&gt;
&lt;span class="c1"&gt;# Post Load ...&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;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# (no query)&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;likes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="c1"&gt;# (no query)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Conclusion: Start with &amp;ldquo;size&amp;rdquo;&lt;/h2&gt;

&lt;p&gt;Most people recommend to use &lt;code&gt;.size&lt;/code&gt; unless you have a reason for not using it, because most of the time it will do what you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will use the counter cache if is there&lt;/li&gt;
&lt;li&gt;It will count with ruby if the records are already loaded&lt;/li&gt;
&lt;li&gt;It will perform an SQL &lt;code&gt;COUNT&lt;/code&gt; if the records are not already loaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you don&amp;rsquo;t have a counter cache and you need to use the association later in the view, one tip from the &lt;a href="https://www.speedshop.co/2019/01/10/three-activerecord-mistakes.html#count-executes-a-count-every-time"&gt;Nate Berkopec&lt;/a&gt;, is to use &lt;code&gt;.load.size&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Reference&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-count"&gt;count&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-length"&gt;length&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Associations/CollectionProxy.html#method-i-size"&gt;size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Active record playground, for rails developers that want an easy way to practice with active record in isolation</title>
    <link rel="alternate" href="https://bhserna.com/active-record-playground"/>
    <id>https://bhserna.com/active-record-playground</id>
    <published>2022-03-29T12:21:00Z</published>
    <updated>2022-03-29T12:21:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I want to share with you my “Active record playground template”. &lt;/p&gt;

&lt;p&gt;It is a template that you could use as a guide or by cloning the repo and
following the instructions in the README if you want to play with some Active
Record models in isolation and without creating a full rails app.&lt;/p&gt;

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

&lt;h2&gt;I was repeating my self over and over&lt;/h2&gt;

&lt;p&gt;I was setting up the structure of this template from scratch over and over
again until I decided to do it in a little more general way.&lt;/p&gt;

&lt;p&gt;Now it is much more faster and easier for me to start playing with Active
Record in isolation. And now, I hope it also can help you.&lt;/p&gt;

&lt;p&gt;The default structure of this template will make some decision for you:&lt;/p&gt;

&lt;h2&gt;1. The starting Gemfile&lt;/h2&gt;

&lt;p&gt;It includes activerecord and pg, but also the faker gem, because it will be help you to define your examples data.&lt;/p&gt;

&lt;h2&gt;2. A place for your models&lt;/h2&gt;

&lt;p&gt;On lib/models.rb you can define the objects/records that you need. This file is already required to run your seeds and examples.&lt;/p&gt;

&lt;h2&gt;3. A place for your seeds&lt;/h2&gt;

&lt;p&gt;On db/seed.rb you will be able to define your seeds using the objects defined on lib/models.rb. You will also be able to run them with “ruby db/seed.rb”.&lt;/p&gt;

&lt;h2&gt;4. A place for the schema&lt;/h2&gt;

&lt;p&gt;As this is “playing” code, instead of migration, you will create directly the schema on db/schema.rb.&lt;/p&gt;

&lt;h2&gt;5. The db setup command&lt;/h2&gt;

&lt;p&gt;With “ruby db/setup.rb” you will create or re-create the database, load the schema and run the seeds.&lt;/p&gt;

&lt;h2&gt;6. A place to define your examples&lt;/h2&gt;

&lt;p&gt;You will have an /examples dir, to add the examples files that you need.&lt;/p&gt;

&lt;p&gt;Requiring examples/config will require the database initialization code and the models defined on lib/models.rb&lt;/p&gt;

&lt;p&gt;You can run the examples with &lt;code&gt;ruby examples/&amp;lt;file name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby example/00_example.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Use the template&lt;/h2&gt;

&lt;p&gt;Use this template if you want to play with Active Record in isolation without creating a full rails app.&lt;/p&gt;

&lt;p&gt;You can find the template on &lt;a href="https://github.com/bhserna/active_record_playground"&gt;https://github.com/bhserna/active_record_playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use it as a guide or by cloning the repo and following the instructions on the README.&lt;/p&gt;

&lt;h2&gt;Some examples of using this template&lt;/h2&gt;

&lt;p&gt;Here are some examples that I have shared, using this template (or previous versions of it):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/specific_preloading_with_scoped_associations"&gt;github.com/bhserna/specific_preloading_with_scoped_associations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bhserna/first_or_last_per_user"&gt;github.com/bhserna/first_or_last_per_user&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Specific preloading with scoped associations</title>
    <link rel="alternate" href="https://bhserna.com/specific-preloading-with-scoped-associations"/>
    <id>https://bhserna.com/specific-preloading-with-scoped-associations</id>
    <published>2022-03-16T12:44:00Z</published>
    <updated>2022-03-16T12:44:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;It is very common the need to preload an association then filter that association
and then use the filtered collection.&lt;/p&gt;

&lt;p&gt;For example, imagine that you need to render a list of posts with just its
&amp;ldquo;popular&amp;rdquo; comments, where &amp;ldquo;popular&amp;rdquo; means &amp;ldquo;with a &lt;code&gt;likes_count&lt;/code&gt; greater than or
equals to X&amp;rdquo;.&lt;/p&gt;

&lt;h2&gt;Approach without scoped association&lt;/h2&gt;

&lt;p&gt;So you can define &lt;code&gt;Comment#popular?&lt;/code&gt; like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="no"&gt;POPULAR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;popular?&lt;/span&gt;
    &lt;span class="n"&gt;likes_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="no"&gt;POPULAR&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;p&gt;And so you can sometimes, complete your task, avoiding n+1 queries, by
preloading the comments and then selecting just the &amp;ldquo;popular&amp;rdquo; comments with
ruby.&lt;/p&gt;

&lt;p&gt;Something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Preload all comments for each post&lt;/span&gt;
&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "posts".* FROM "posts" LIMIT $1&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5)&lt;/span&gt;

&lt;span class="n"&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="n"&gt;render_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Select just the popular comments&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;comments&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:popular?&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;render_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Possible memory problems&lt;/h2&gt;

&lt;p&gt;This can work on many situations, but sometimes it can give you some troubles,
with memory, if the association that you are preloading has a lot of records,
because when you preload an association rails will instantiate all the
associated records.&lt;/p&gt;

&lt;h2&gt;Solution with scoped association&lt;/h2&gt;

&lt;p&gt;One way of solving this problem with rails is to create an association passing an
scope, to filter the associated records.&lt;/p&gt;

&lt;p&gt;For example, in this problem, you can add a &lt;code&gt;popular_comments&lt;/code&gt; associations, to help
you preload just the comments that you are going to need.&lt;/p&gt;

&lt;p&gt;Like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="c1"&gt;# Here you are defining an association, that will use Comment.popular,&lt;/span&gt;
  &lt;span class="c1"&gt;# to filter the Comment records.&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;popular_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;popular&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="no"&gt;POPULAR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;

  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="c1"&gt;# Here we are definining what `popular` means.&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;popular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;likes_count: &lt;/span&gt;&lt;span class="no"&gt;POPULAR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;p&gt;And then use the &lt;code&gt;popular_comments&lt;/code&gt; association to preload just the &amp;ldquo;popular&amp;rdquo;
comments instead of all the comments for each post, and then use the
&lt;code&gt;popular_comments&lt;/code&gt; method to display just the &amp;ldquo;popular&amp;rdquo; comments.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Preload directly just the popular comments&lt;/span&gt;
&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:popular_comments&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "posts".* FROM "posts" LIMIT $1  [["LIMIT", 5]]&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; SELECT "comments".* FROM "comments" WHERE "comments"."likes_count" &amp;gt;= $1 AND "comments"."post_id" IN ($1, $2, $3, $4, $5)&lt;/span&gt;

&lt;span class="n"&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="n"&gt;render_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# You can call the association for each post without n+1 queries&lt;/span&gt;
  &lt;span class="c1"&gt;# because the association has already been preloaded&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;popular_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;render_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this way you can use your defined scopes, to build &amp;ldquo;scoped&amp;rdquo; associations to
help you preload just the records that your are going to need and save some
memory.&lt;/p&gt;

&lt;h2&gt;Run the examples&lt;/h2&gt;

&lt;p&gt;You can find a repo with the examples from this posts, on &lt;a href="https://github.com/bhserna/specific_preloading_with_scoped_associations"&gt;github.com/bhserna/specific_preloading_with_scoped_associations&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to use react with hotwire?</title>
    <link rel="alternate" href="https://bhserna.com/use-react-with-hotwire"/>
    <id>https://bhserna.com/use-react-with-hotwire</id>
    <published>2022-02-28T23:14:00Z</published>
    <updated>2022-02-28T23:14:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I have seen that some people think that you have to forget about Rails and Hotwire, if you want to use React or another Javascript framework to build a Single Page Aplication, but this idea is not true.&lt;/p&gt;

&lt;p&gt;You can use Rails and Hotwire as the base of your app, and plug your React app to be shown where is needed. In this way you can use both frameworks, and pick the one that you think that will help you must for each feature.&lt;/p&gt;

&lt;p&gt;Here I want to explain how you can plug a react app inside a Rails app with Hotwire, using Stimulus.js.&lt;/p&gt;

&lt;h3&gt;You will need a javascript bundler&lt;/h3&gt;

&lt;p&gt;To use a javascript bundler with rails, you can use the gem &lt;a href="https://github.com/rails/jsbundling-rails"&gt;jsbundling-rails&lt;/a&gt;, it will help you to integrate &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt;, &lt;a href="https://rollupjs.org/"&gt;rollup.js&lt;/a&gt;, or &lt;a href="https://webpack.js.org/"&gt;Webpack&lt;/a&gt; to bundle your JavaScript, then deliver it via the asset pipeline in Rails.&lt;/p&gt;

&lt;p&gt;If you are already familiar with one of those, use it, if you are not familiar, or you want to move away from webpack, I think that you could try esbuild.&lt;/p&gt;

&lt;p&gt;I will continue this article explaining who you can integrate react using esbuild.&lt;/p&gt;

&lt;h3&gt;Install jsbundler and esbuild tool&lt;/h3&gt;

&lt;p&gt;Following the instructions from the &lt;a href="https://github.com/rails/jsbundling-rails"&gt;github&lt;/a&gt; page, you will need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;jsbundling-rails&lt;/code&gt; to your Gemfile with &lt;code&gt;gem &amp;#39;jsbundling-rails&amp;#39;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;./bin/bundle install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;./bin/rails javascript:install:esbuild&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After this steps the installer should have updated your &lt;code&gt;package.json&lt;/code&gt; adding &lt;code&gt;esbuild&lt;/code&gt; as dependency and a build script, with the instruction to build your javascript from &lt;code&gt;app/javascript&lt;/code&gt; and put it on &lt;code&gt;app/assets/builds&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight diff"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;  "name": "app",
   "private": "true",
   "dependencies": {
     "@hotwired/stimulus": "^3.0.1",
&lt;span class="gd"&gt;-    "@hotwired/turbo-rails": "^7.1.1"
&lt;/span&gt;&lt;span class="gi"&gt;+    "@hotwired/turbo-rails": "^7.1.1",
+    "esbuild": "^0.14.18"
+  },
+  "scripts": {
+    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
+  }
&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Install react and react dom&lt;/h3&gt;

&lt;p&gt;Now you can go and install react with yarn:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;yarn add react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will update you package.json with &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react-dom&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight diff"&gt;&lt;span class="err"&gt;{
&lt;/span&gt;  "name": "app",
   "private": "true",
   "dependencies": {
     "@hotwired/stimulus": "^3.0.1",
     "@hotwired/turbo-rails": "^7.1.1",
&lt;span class="gd"&gt;-    "esbuild": "^0.14.18"
&lt;/span&gt;&lt;span class="gi"&gt;+    "esbuild": "^0.14.18",
+    "react": "^17.0.2",
+    "react-dom": "^17.0.2"
&lt;/span&gt;   },
   "scripts": {
     "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;The hello world&lt;/h3&gt;

&lt;p&gt;You can now add a &lt;code&gt;app/javasctipts/App.jsx&lt;/code&gt; file to test the react integration. There you can add something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;import * as React from 'react'

export default () =&amp;gt; &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now add a &lt;code&gt;app/javascript/controllers/hello_controller.jsx&lt;/code&gt; that to display the &lt;code&gt;App&lt;/code&gt; component on &lt;code&gt;connect&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: 👉 If you already have one &lt;code&gt;hello_controller.jsx&lt;/code&gt;, because you are on a new rails app, you can just change the file extension to &lt;code&gt;.jsx&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;import { Controller } from "@hotwired/stimulus"
import * as React from 'react'
import * as ReactDOM from "react-dom";
import App from "./../app"

export default class extends Controller {
  connect() {
    ReactDOM.render(&amp;lt;App /&amp;gt;, this.element);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now on a page add a tag for the  &lt;code&gt;hello_controller&lt;/code&gt; to display the react &lt;code&gt;App&lt;/code&gt; component.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this moment you should see &amp;ldquo;Hello world!&amp;rdquo; on the page if you visit the page.&lt;/p&gt;

&lt;p&gt;By starting the react app with Stimulus.js we can then move to other pages, that don&amp;rsquo;t have any react component, and then go back to the page with react using turbo, and Stimulus.js will initialize the react app as expected.&lt;/p&gt;

&lt;h3&gt;Adding more than one file&lt;/h3&gt;

&lt;p&gt;You can add more &lt;code&gt;jsx&lt;/code&gt; files, directly on &lt;code&gt;app/javascripts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example if you add a &lt;code&gt;Counter.jsx&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;import * as React from 'react';
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    &amp;lt;button onClick={handleClick}&amp;gt;
      Clicked {count} times
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then import it on the &lt;code&gt;App.jsx&lt;/code&gt; with&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;import * as React from 'react'
import Counter from "./Counter";

export default () =&amp;gt; &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And with that you can now can use that &lt;code&gt;Counter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, we can change the &lt;code&gt;App&lt;/code&gt; function to something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;import * as React from 'react'
import Counter from "./Counter";

export default function App() {
  let style = {
    border: "1px solid red",
    padding: "1rem"
  }

  return (
    &amp;lt;div style={style}&amp;gt;
      &amp;lt;h1&amp;gt;This is a React App!&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This are some react counters!&amp;lt;/p&amp;gt;

      &amp;lt;Counter /&amp;gt;
      &amp;lt;Counter /&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To show something like this&amp;hellip;&lt;/p&gt;

&lt;p&gt;&lt;img alt="This is a react app" src="/use-react-with-hotwire/this-is-a-react-app.png" /&gt;&lt;/p&gt;

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

&lt;p&gt;I built a little example app with the last example.&lt;/p&gt;

&lt;p&gt;You can get the code of the example app on &lt;a href="https://github.com/bhserna/hotwire-react"&gt;github.com/bhserna/hotwire-react&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is a demo that shows how you can plug a react app inside a rails app using Stimulus.js.&lt;/p&gt;

&lt;p&gt;If you go to the &amp;ldquo;page with react&amp;rdquo; you will see a page with the counters implemented using react and started using a Stimulus controller.&lt;/p&gt;

&lt;p&gt;As we saw in this post, by starting the react app with Stimulus.js you can then move back to the index, that don&amp;rsquo;t have any react component, and the go back to the page with react using turbo, and Stimulus.js will initialize the app as expected.&lt;/p&gt;

&lt;p&gt;Like in this video&amp;hellip;&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 62.5%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/02454214bbbe45ef96cf7a821c64b606" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Should you split your rails app into backend and frontend?</title>
    <link rel="alternate" href="https://bhserna.com/should-you-split-your-rails-app-into-backend-and-frontend"/>
    <id>https://bhserna.com/should-you-split-your-rails-app-into-backend-and-frontend</id>
    <published>2022-02-14T23:23:00Z</published>
    <updated>2022-02-14T23:23:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When you start a new app or when things start to get complicated on the
frontend  side of your rails app, I think that it is normal to start thinking
if you should split the app into a rails backend and a frontend with react,
  vue, or other javascript framework.&lt;/p&gt;

&lt;p&gt;I am a rails developer that normally prefers the “majestic monolith”, and I
will normally recommend to not doing it, and even I sometimes think if I should
split my apps or not. But I think that at least in my this normally caused by
hype or frustration.&lt;/p&gt;

&lt;p&gt;So this is a list for you and for me, with some things to consider when you have &amp;ldquo;reasons&amp;rdquo; for splitting your app like&amp;hellip;&lt;/p&gt;

&lt;h2&gt;Reason: UI becomes more complex and we need/want a smooth user experience&lt;/h2&gt;

&lt;p&gt;Maybe you are thinking that with rails every interaction will be server side rendering&amp;hellip;.&lt;/p&gt;

&lt;p&gt;That is not really true, rails by default propose server side rendering, but if some parts of your app requires a more complex interaction, you can always use a javascript framework for those parts, and with stimulus.js you will be able to plug that behavior in a “rails way”.&lt;/p&gt;

&lt;p&gt;The talk &lt;a href="https://www.youtube.com/watch?v=SWEts0rlezA"&gt;Turbolinks 5: I Can’t Believe It’s Not Native!&lt;/a&gt; by &lt;a href="https://twitter.com/sstephenson"&gt;Sam Stephenson&lt;/a&gt;  describes this idea back on the “Turbolinks 5” times, but now the environment is much better.&lt;/p&gt;

&lt;h2&gt;Reason: The UI requires complex interactions on many parts of the app&lt;/h2&gt;

&lt;p&gt;Are those parts the majority of the app? Do you want to increase the complexity of the whole app for does parts? Can you plug those apps with stimulus.js?&lt;/p&gt;

&lt;h2&gt;Reason: We already have Vue/JQuery/Javascript code all over the place and it&amp;rsquo;s a mess&lt;/h2&gt;

&lt;p&gt;Was the mess caused by not splitting? Could stimulus.js and turbo help to reduce that complexity?&lt;/p&gt;

&lt;p&gt;After that mess&amp;hellip; Do you now have and idea of how you can structure your code better?&lt;/p&gt;

&lt;h2&gt;Reason: It is easier to test an api&lt;/h2&gt;

&lt;p&gt;When our system tests start getting slow and complicated we can think that is easier to have two separate test suites, one for the api and one for the front end app.&lt;/p&gt;

&lt;p&gt;And maybe it is true that is easier to test both things as a separate thing, but it is just easier as it is easier to write a test for a single method than a system test.&lt;/p&gt;

&lt;p&gt;If you split are you going to avoid system tests that execute through both parts? Are you going to feel the same confidence?&lt;/p&gt;

&lt;h2&gt;Reason: The backend will be leaner (less gems etc) and hopefully faster&lt;/h2&gt;

&lt;p&gt;Maybe this could be true, but how much? What about the performance of the front end app? What about the complexity of the whole project? What about the whole set of dependencies including the javascript dependencies?&lt;/p&gt;

&lt;h2&gt;Reason: It’s super hard to find great full stack rails developers&lt;/h2&gt;

&lt;p&gt;This is true, but I think that there is no easy path, finding good engineers is not an easy job.&lt;/p&gt;

&lt;p&gt;It will be hard to find a great full stack rails developer, but it will also be hard to find a great frontend developer and/or backend developer.&lt;/p&gt;

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

&lt;p&gt;As you have seen I am very biased to the monolith, maybe this should not be your only reference for this decision, but I hope it could be useful in some way.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How the turbo_frame_tag helper transforms an object to an html id?</title>
    <link rel="alternate" href="https://bhserna.com/turbo_frame_tag-and-dom_id"/>
    <id>https://bhserna.com/turbo_frame_tag-and-dom_id</id>
    <published>2022-02-08T23:24:00Z</published>
    <updated>2022-02-08T23:24:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;If you are new with rails and hotwire, it could be useful to know that you can pass an object to the &lt;code&gt;turbo_frame_tag&lt;/code&gt; and it will use a string representation of that object as an id.&lt;/p&gt;

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

&lt;p&gt;For example if you have a &lt;code&gt;Product&lt;/code&gt; record, you can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And it will return:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"product_1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;/turbo_frame&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if you do &lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will return:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"new_product"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/turbo_frame&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This happen because internally &lt;code&gt;turbo_frame_tag&lt;/code&gt; uses the &lt;code&gt;dom_id&lt;/code&gt; helper.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can see the code of the &lt;code&gt;turbo_frame_tag&lt;/code&gt; helper here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hotwired/turbo-rails/blob/v1.0.1/app/helpers/turbo/frames_helper.rb"&gt;https://github.com/hotwired/turbo-rails/blob/v1.0.1/app/helpers/turbo/frames_helper.rb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the docs for the &lt;code&gt;dom_id&lt;/code&gt; helper here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActionView/RecordIdentifier.html"&gt;https://api.rubyonrails.org/classes/ActionView/RecordIdentifier.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Styler and tachyons examples</title>
    <link rel="alternate" href="https://bhserna.com/styler-tachyons-examples"/>
    <id>https://bhserna.com/styler-tachyons-examples</id>
    <published>2022-01-28T23:30:00Z</published>
    <updated>2022-01-28T23:30:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I built a &lt;a href="styler-a-tool-to-compose-css-classes-with-ruby.html"&gt;tool&lt;/a&gt; to comples css classes like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_red"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 white bg_blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 white bg_red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;hellip; because I think that, although &lt;a href="https://css-tricks.com/css-is-awesome/"&gt;css is
awesome&lt;/a&gt; (really!), I think that it
misses is the ability to compose already defined styles, to define new ones.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="styler-a-tool-to-compose-css-classes-with-ruby.html"&gt;introduction post&lt;/a&gt;
and on &lt;a href="https://github.com/bhserna/styler/"&gt;github&lt;/a&gt;, you can find a tutorial of
how the tool works.&lt;/p&gt;

&lt;p&gt;But until now there was no &amp;ldquo;real&amp;rdquo; examples of how to use it.&lt;/p&gt;

&lt;h2&gt;A collection of examples&lt;/h2&gt;

&lt;p&gt;That is why I am building a site with some examples from the list of &lt;a
href="https://tachyons.io/components/"&gt;tachyons components&lt;/a&gt; from the
Tachyons&amp;rsquo;s page, to show you how you can use the the gem to define the styles.&lt;/p&gt;

&lt;p&gt;You can visit the site on &lt;a href="http://styler-tachyons-examples.bhserna.com"&gt;styler-tachyouns-examples.bhserna.com&lt;/a&gt;.&lt;/p&gt;

&lt;iframe src="https://styler-tachyons-examples.bhserna.com" width="100%" height="500px" class="ba b--dotted b--light-blue"&gt;&lt;/iframe&gt;

&lt;h2&gt;The code of the site&lt;/h2&gt;

&lt;p&gt;If you want to help to add more examples, or you want to see how I used the
tool to &lt;a href="https://github.com/bhserna/styler_tachyons_examples/blob/main/helpers/styles_helper.rb"&gt;style the
site&lt;/a&gt;,
or if you are curious about how it was build, you can see the code on
&lt;a href="https://github.com/bhserna/styler_tachyons_examples"&gt;github/bhserna/styler_tachyons_examples&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;How do you think that this tool can help you?&lt;/h2&gt;

&lt;p&gt;I know that maybe it is a little unexpected way of doing things, but if you
have have some ideas on how this tool can help you it would be nice to know
them.&lt;/p&gt;

&lt;p&gt;You can write a comment here or tell me via &lt;a href="https://twitter.com/bhserna"&gt;twitter&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Inline CRUD with rails and hotwire</title>
    <link rel="alternate" href="https://bhserna.com/inline-crud-hotwire"/>
    <id>https://bhserna.com/inline-crud-hotwire</id>
    <published>2022-01-11T00:08:00Z</published>
    <updated>2022-01-11T00:08:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;This is a guide to help you build &amp;ldquo;inline CRUDs&amp;rdquo; with rails and hotwire, where
you create and edit records in the same page.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Instead of a &amp;ldquo;new product&amp;rdquo; page it shows the form in the same page&lt;/li&gt;
&lt;li&gt;Instead of an &amp;ldquo;edit product&amp;rdquo; page it shows the form on the same page by replacing the product item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like in the next video&amp;hellip;&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 62.5%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/d3cae370e9fa44109daa5afa67285575" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;The example app&lt;/h2&gt;

&lt;p&gt;You can find the code of the app in the video on: &lt;a href="https://github.com/bhserna/inline_crud_hotwire"&gt;github/bhserna/inline_crud_hotwire&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To explain how you can add this behavior to your app, I will use the
&lt;a href="https://www.loom.com/share/d3cae370e9fa44109daa5afa67285575?t=0"&gt;app&lt;/a&gt; from the
&lt;a href="https://www.loom.com/share/d3cae370e9fa44109daa5afa67285575?t=0"&gt;video&lt;/a&gt; as an
example.&lt;/p&gt;

&lt;p&gt;If you need help to extrapolate the example for your use case, you can ask a
question in the comments section of this post, I will try to answer there.&lt;/p&gt;

&lt;h2&gt;Inline CRUD interactions&lt;/h2&gt;

&lt;p&gt;I will try to explain the relevant parts on the interaction with turbo that will help you build an &amp;ldquo;inline CRUD&amp;rdquo;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#new_form"&gt;Show the &amp;ldquo;new product&amp;rdquo; form in the same page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create_success"&gt;Show the product after it is created&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create_error"&gt;Show the form errors on the &amp;ldquo;new product&amp;rdquo; form&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#edit_form"&gt;Show the &amp;ldquo;edit product&amp;rdquo; form on the item&amp;rsquo;s place&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#update_success"&gt;Show the product after it is updated&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#update_error"&gt;Show the form errors on the &amp;ldquo;edit product&amp;rdquo; form&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#delete"&gt;Delete the product&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="new_form"&gt; Show the &amp;ldquo;new product&amp;rdquo; form in the same page&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;%= turbo_frame_tag &amp;quot;new_product&amp;quot;, target: &amp;quot;_top&amp;quot; %&amp;gt;&lt;/code&gt; on the index page.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;data-turbo-frame=&amp;quot;new_product&amp;quot;&lt;/code&gt; to the &amp;ldquo;New product&amp;rdquo; link.&lt;/li&gt;
&lt;li&gt;On the &lt;code&gt;new&lt;/code&gt; template wrap the form on a &lt;code&gt;&amp;lt;%= turbo_frame_tag @product do %&amp;gt;...&amp;lt;% end %&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="c1"&gt;# products/index.html.erb &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;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"new_product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"_top"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="c1"&gt;# products/index.html.erb &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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"New product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_product_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_frame: &lt;/span&gt;&lt;span class="s2"&gt;"new_product"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="c1"&gt;# products/new.html.erb &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;turbo_frame_tag&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;p&gt;The &lt;code&gt;&amp;lt;%= turbo_frame_tag &amp;quot;new_product&amp;quot;, target: &amp;quot;_top&amp;quot; %&amp;gt;&lt;/code&gt; on the index page
will be converted to a &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;new_product&amp;quot; target=&amp;quot;_top&amp;quot;&amp;gt;&lt;/code&gt;. It will
set the place for the &amp;ldquo;new product&amp;rdquo; form.&lt;/p&gt;

&lt;p&gt;The id is &lt;code&gt;new_product&lt;/code&gt; because it will help us to latter use the method
&lt;code&gt;turbo_frame_tag @product&lt;/code&gt; where &lt;code&gt;@product&lt;/code&gt; is &lt;code&gt;Product.new&lt;/code&gt;, then rails will
turn this into the string &lt;code&gt;&amp;quot;new_product&amp;quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;target=&amp;quot;_top&amp;quot;&lt;/code&gt; is there to &lt;a href="https://turbo.hotwired.dev/reference/frames#frame-targeting-the-whole-page-by-default"&gt;target the whole page by
default&lt;/a&gt;
and in that way when the product is successfully created you can redirect to the
index page and turbo will update the whole page.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;data-turbo-frame=&amp;quot;new_product&amp;quot;&lt;/code&gt; attribute on the &amp;ldquo;New product&amp;rdquo; link. Tells
turbo to put the content of the matching turbo frame from the server response
on the &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;new_product&amp;quot;&amp;gt;&lt;/code&gt; that is on the current page.&lt;/p&gt;

&lt;p&gt;This is why on the &lt;code&gt;new.html.erb&lt;/code&gt; template the form is inside &lt;code&gt;&amp;lt;%=
turbo_frame_tag @product do %&amp;gt;&lt;/code&gt;, that will be transformed by rails to
&lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;new_product&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="create_success"&gt;Show the product after it is created&lt;/h2&gt;

&lt;p&gt;Redirect to the &lt;code&gt;index&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;If the turbo frame has the attribute &lt;code&gt;target=&amp;quot;_top&amp;quot;&lt;/code&gt; to &lt;a href="https://turbo.hotwired.dev/reference/frames#frame-targeting-the-whole-page-by-default"&gt;target the whole page
by default&lt;/a&gt;
you can redirect to the index page on the controller and turbo will update the
whole page.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="create_error"&gt;Show the form errors on the &amp;ldquo;new product&amp;rdquo; form&lt;/h2&gt;

&lt;p&gt;Update the form with a &lt;code&gt;turbo_stream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the turbo frame has the attribute &lt;code&gt;target=&amp;quot;_top&amp;quot;&lt;/code&gt; to &lt;a href="https://turbo.hotwired.dev/reference/frames#frame-targeting-the-whole-page-by-default"&gt;target the whole page
by default&lt;/a&gt;
you can&amp;rsquo;t render &lt;code&gt;new.html.erb&lt;/code&gt; because then turbo will update the whole page with the response.&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s why you need to create a new template &lt;code&gt;new.turbo_stream.erb&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;p&gt;Then Rails will change the &lt;code&gt;&amp;lt;%= turbo_stream.update @product do %&amp;gt;&lt;/code&gt; to
&lt;code&gt;&amp;lt;turbo-stream action=&amp;quot;update&amp;quot; target=&amp;quot;new_product&amp;quot;&amp;gt;&lt;/code&gt; because the &lt;code&gt;@product&lt;/code&gt;
was not saved and its dom id representation is still &lt;code&gt;&amp;quot;new_product&amp;quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the controller you can respond with &lt;code&gt;render :new, status:
:unprocessable_entity&lt;/code&gt; and as the request was a &lt;code&gt;TURBO_STREAM&lt;/code&gt; request rails
will use the &lt;code&gt;turbo_stream&lt;/code&gt; template by default.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kp"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&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;h2 id="edit_form"&gt;Show the &amp;ldquo;edit product&amp;rdquo; form on the item&amp;rsquo;s place&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Wrap the list item in a &lt;code&gt;&amp;lt;%= turbo_frame_tag product %&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On the &lt;code&gt;edit&lt;/code&gt; template wrap the form on a &lt;code&gt;&amp;lt;%= turbo_frame_tag @product do %&amp;gt;...&amp;lt;% end %&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wrapping the list item in a &lt;code&gt;&amp;lt;%= turbo_frame_tag product %&amp;gt;&lt;/code&gt;, creates a
&lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s2"&gt;"Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edit_product_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;p&gt;This will tell turbo to capture all navigation within the frame by expecting
any followed link or form submission to &lt;strong&gt;return a response including a
matching frame tag&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Wrapping the product edition form, on a &lt;code&gt;&amp;lt;%= turbo_frame_tag @product do
%&amp;gt;...&amp;lt;% end %&amp;gt;&lt;/code&gt; will create the matching &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="c1"&gt;# products/edit.html.erb &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;turbo_frame_tag&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;p&gt;Then turbo will take the content from the matching &lt;code&gt;&amp;lt;turbo-frame&amp;gt;&lt;/code&gt; the
response, and update the content of the current &lt;code&gt;&amp;lt;turbo-frame&amp;gt;&lt;/code&gt; on the page.&lt;/p&gt;

&lt;h2 id="update_success"&gt;Show the product after it is updated&lt;/h2&gt;

&lt;p&gt;Redirect to the &lt;code&gt;index&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;As the form was inside the &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt; and the
index page has a matching &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt;, turbo
will take that content and update the form with the regular list item from the
index page.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="update_error"&gt;Show the form errors on the &amp;ldquo;edit product&amp;rdquo; form&lt;/h2&gt;

&lt;p&gt;Render the &lt;code&gt;edit.html.erb&lt;/code&gt; template with an error status.&lt;/p&gt;

&lt;p&gt;When you submit a form, tubo will expect a redirect, but if you return an error
status, turbo will process the response. So if you respond with &lt;code&gt;render :edit,
status: :unprocessable_entity&lt;/code&gt; turbo will take the content from the matching
&lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt;, and update the page.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&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;h2 id="delete"&gt;Delete the product&lt;/h2&gt;

&lt;p&gt;Redirect to the &lt;code&gt;index&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;As the delete button, that is really an html form, was inside the &lt;code&gt;&amp;lt;turbo-frame
id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt; and the index page (after the item was deleted), will not have a matching &lt;code&gt;&amp;lt;turbo-frame
id=&amp;quot;product_#{product.id}&amp;quot;&amp;gt;&lt;/code&gt;, turbo will remove the content from &lt;code&gt;&amp;lt;turbo-frame&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that it will not remove the turbo frame from the page, it will just remove
its content. If you want to remove the turbo frame you will need to respond
with a &lt;code&gt;turbo-stream&lt;/code&gt; to remove the &lt;code&gt;turbo-frame&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Remote modals with rails, hotwire and bootstrap</title>
    <link rel="alternate" href="https://bhserna.com/remote-modals-with-rails-hotwire-and-bootstrap"/>
    <id>https://bhserna.com/remote-modals-with-rails-hotwire-and-bootstrap</id>
    <published>2022-01-04T13:08:00Z</published>
    <updated>2022-01-04T13:08:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Here I want to show you how you can build “remote modals” (modals where the content is requested to the server), submit forms embedded in them and handle errors, with rails, hotwire and bootstrap.&lt;/p&gt;

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

&lt;h2&gt;Example app&lt;/h2&gt;

&lt;p&gt;I will show you how to build is something like this&amp;hellip;&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 62.5%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/bcc3514ebafc4665874098bf8386cd1f" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;Where you can&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click a link that requests content to the server and then shown inside a modal.&lt;/li&gt;
&lt;li&gt;Submit a form and display the errors inside the modal&lt;/li&gt;
&lt;li&gt;If the form is processed with success, the redirect to the expected page.&lt;/li&gt;
&lt;li&gt;Keep the &amp;ldquo;fade&amp;rdquo; effect of the bootstrap modal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like a standard rails CRUD, but using modals to show the form.&lt;/p&gt;

&lt;h2&gt;Play with the code&lt;/h2&gt;

&lt;p&gt;The code of the app is on
&lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap"&gt;github.com/bhserna/remote modals hotwire bootstrap&lt;/a&gt;,
you can clone it and play with it.&lt;/p&gt;

&lt;h2&gt;Versions&lt;/h2&gt;

&lt;p&gt;For the dependencies that I will talk about here, I am using this versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby: 3.0.0&lt;/li&gt;
&lt;li&gt;Rails: 7.0.0&lt;/li&gt;
&lt;li&gt;Turbo rails: 1.0.0&lt;/li&gt;
&lt;li&gt;Stimulus rails: 1.0.2&lt;/li&gt;
&lt;li&gt;Bootstrap: 5.1.3&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Show a form in modal&lt;/h2&gt;

&lt;p&gt;In the example app you can see this action twice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the &amp;ldquo;New product&amp;rdquo; link&lt;/li&gt;
&lt;li&gt;And on the &amp;ldquo;Edit&amp;rdquo; link &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let&amp;rsquo;s see how the &amp;ldquo;New product&amp;rdquo; link works&amp;hellip;&lt;/p&gt;

&lt;p&gt;If you go to
&lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/products/index.html.haml"&gt;views/products/index.html.haml&lt;/a&gt;,
you will see that the link has a &lt;code&gt;data-turbo-frame=&amp;quot;remote_modal&amp;quot;&lt;/code&gt; attribute.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s2"&gt;"New product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_product_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;turbo_frame: &lt;/span&gt;&lt;span class="s2"&gt;"remote_modal"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will tell turbo, to look for an already defined &lt;code&gt;turbo_frame_tag&lt;/code&gt; with the
id &lt;code&gt;remote_modal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This tag is defined on
&lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/layouts/application.html.erb#L17"&gt;views/layouts/application.html.erb&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"_top"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With that tag defined, turbo will expect a server response with a matching
&lt;code&gt;turbo_frame_tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means that a &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote_modal&amp;quot;&amp;gt;&lt;/code&gt; element should be somewhere
in the server response.&lt;/p&gt;

&lt;p&gt;This is implemented like this&amp;hellip;&lt;/p&gt;

&lt;p&gt;If you see the &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/controllers/products_controller.rb#L7"&gt;products
controller&lt;/a&gt;
you will see that it just sets the &lt;code&gt;@product&lt;/code&gt; instance variable and renders the default template &lt;code&gt;new.html&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On
&lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/products/new.html.haml#L1"&gt;view/products/new.html.haml&lt;/a&gt;
you will see that it renders the &lt;code&gt;&amp;quot;remote_modal&amp;quot;&lt;/code&gt; partial.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"New product"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="vi"&gt;@product&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This partial is on
&lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/application/_remote_modal.html.haml"&gt;views/application/_remote_modal.html.haml&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nc"&gt;.modal.fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"remote-modal"&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"turbo:before-render@document-&amp;gt;remote-modal#hideBeforeRender"&lt;/span&gt;)
    &lt;span class="nc"&gt;.modal-dialog&lt;/span&gt;
      &lt;span class="nc"&gt;.modal-content&lt;/span&gt;
        &lt;span class="nc"&gt;.modal-header&lt;/span&gt;
          &lt;span class="nt"&gt;%h5&lt;/span&gt;&lt;span class="nc"&gt;.modal-title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
          &lt;span class="nt"&gt;%button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn-close"&lt;/span&gt; &lt;span class="na"&gt;data-bs-dismiss=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close"&lt;/span&gt;)
        &lt;span class="nc"&gt;.modal-body&lt;/span&gt;&lt;span class="nf"&gt;#remote_modal_body&lt;/span&gt;
          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see it wraps the modal html within the expected &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote_modal&amp;quot;&amp;gt;&lt;/code&gt; element with the code&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The html is almost the standard modal template from bootstrap. But with some modifications&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It expects a &lt;code&gt;title&lt;/code&gt; local variable.&lt;/li&gt;
&lt;li&gt;It &lt;code&gt;yield&lt;/code&gt; on the &lt;code&gt;.modal-body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It initializes a &lt;code&gt;remote-modal&lt;/code&gt; stimulus controller.&lt;/li&gt;
&lt;li&gt;It defines an action on the stimulus controller that calls &lt;code&gt;remote-modal#hideBeforeRender&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It defines a &lt;code&gt;#remote_modal_body&lt;/code&gt; html id.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point the things that are required are the first three&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It expects a &lt;code&gt;title&lt;/code&gt; local variable.&lt;/li&gt;
&lt;li&gt;It &lt;code&gt;yield&lt;/code&gt; on the &lt;code&gt;.modal-body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It initializes a &lt;code&gt;remote-modal&lt;/code&gt; stimulus controller.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here I am passing the &lt;code&gt;title&lt;/code&gt; and putting the &lt;code&gt;yield&lt;/code&gt; on the &lt;code&gt;.modal-body&lt;/code&gt; just
because I think that in this example app it makes scense, but maybe on your app it
will be better to just &lt;code&gt;yield&lt;/code&gt; on &lt;code&gt;.modal-content&lt;/code&gt; and define the
&lt;code&gt;.modal-header&lt;/code&gt; and &lt;code&gt;.modal-body&lt;/code&gt; on the passed block.&lt;/p&gt;

&lt;p&gt;The attribute &lt;code&gt;data-controller=&amp;quot;remote-modal&amp;quot;&lt;/code&gt; initializes the &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/javascript/controllers/remote_modal_controller.js"&gt;remote modal
stimulus
controller&lt;/a&gt; that on &lt;code&gt;connect&lt;/code&gt;
will initialize a &lt;code&gt;bootstrap.Modal&lt;/code&gt; object for the &lt;code&gt;element&lt;/code&gt; and &lt;code&gt;show&lt;/code&gt; it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"@hotwired/stimulus"&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Modal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"bootstrap"&lt;/span&gt;

&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And&amp;hellip; that&amp;rsquo;s what you need to show a remote modal&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A link with a &lt;code&gt;data-turbo-frame=&amp;quot;remote-modal&amp;quot;&lt;/code&gt; (or whatever id you want).&lt;/li&gt;
&lt;li&gt;An already defined &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote-modal&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A server response with a matching &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote-modal&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A stimulus controller to &amp;ldquo;show&amp;rdquo; the modal, once the content is there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Handing the form redirect and close the modal&lt;/h2&gt;

&lt;p&gt;When the form is processed with success we normally want to redirect to a new
page. On the example app the redirection is to the &lt;code&gt;products_path&lt;/code&gt;, the index
page.&lt;/p&gt;

&lt;p&gt;Be default a &lt;code&gt;turbo_frame&lt;/code&gt; would expect a matching &lt;code&gt;turbo_frame&lt;/code&gt; in the
response, to update the content of the &lt;code&gt;turbo_frame&lt;/code&gt;, but if we want to
redirect and close the modal, we need to &lt;a href="https://turbo.hotwired.dev/reference/frames#frame-targeting-the-whole-page-by-default"&gt;target the whole page by
default&lt;/a&gt;
with a &lt;code&gt;target=&amp;quot;_top&amp;quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Like in the template &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/layouts/application.html.erb#L17"&gt;views/layouts/application.html.erb&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"_top"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That, combined with the redirect on the &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/controllers/products_controller.rb#L10"&gt;create
action&lt;/a&gt;,
will change the content of the whole page and close the modal instantly
(without the fade effect).&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;form_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&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;h2&gt;Close the modal with the fade effect&lt;/h2&gt;

&lt;p&gt;If we want to keep the modal&amp;rsquo;s fade effect that bootstrap provides, we need
somehow tell turbo to pause before it renders the page, to let bootstrap hide
the modal, and then continue with the render after the modal it is
already hidden.&lt;/p&gt;

&lt;p&gt;So, you can listen to the &lt;code&gt;turbo:before-render&lt;/code&gt; event to then&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevent the default behavior.&lt;/li&gt;
&lt;li&gt;Close the modal.&lt;/li&gt;
&lt;li&gt;Resume the render after the modal has been closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the example app the listener to the &lt;code&gt;turbo:before-render&lt;/code&gt; is on the &lt;code&gt;data-action&lt;/code&gt; defined on the &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/application/_remote_modal.html.haml"&gt;remote_modal&lt;/a&gt; partial&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nc"&gt;.modal.fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"remote-modal"&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"turbo:before-render@document-&amp;gt;remote-modal#hideBeforeRender"&lt;/span&gt;)
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the code is telling stimulus, to listen for the &lt;code&gt;turbo:before-render&lt;/code&gt; event on &lt;code&gt;document&lt;/code&gt; and then call the &lt;code&gt;remote-modal#hideBeforeRender&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;That &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/javascript/controllers/remote_modal_controller.js#L14"&gt;method&lt;/a&gt; is defined like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;

  &lt;span class="nx"&gt;hideBeforeRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hidden.bs.modal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the code is&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preventing the default behavior with &lt;code&gt;event.preventDefault()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Then it sets a listener for the &lt;code&gt;hidden.bs.modal&lt;/code&gt; event, that will be triggered
by bootstrap after the modal has been closed, and then call
&lt;code&gt;event.detail.resume&lt;/code&gt; that is &lt;a href="https://turbo.hotwired.dev/handbook/drive#pausing-rendering"&gt;provided by turbo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Then it calls &lt;code&gt;this.modal.hide()&lt;/code&gt; that will tell bootstrap to actually hide the modal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Rendering form errors&lt;/h2&gt;

&lt;p&gt;To render the errors what you need to do is to respond with a turbo stream that
will replace the html of the form.&lt;/p&gt;

&lt;p&gt;As you can see on the &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/controllers/products_controller.rb#L10"&gt;create
action&lt;/a&gt;,
when &lt;code&gt;@product.save&lt;/code&gt; is not successfull, the app will render the
&lt;code&gt;form_update&lt;/code&gt; template and return the &lt;code&gt;:unprocessable_entity&lt;/code&gt; status.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;products_path&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;form_update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&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;p&gt;The &lt;a href="https://github.com/bhserna/remote_modal_hotwire_bootstrap/blob/main/app/views/products/form_update.turbo_stream.erb"&gt;form_update&lt;/a&gt; template looks like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="s2"&gt;"remote_modal_body"&lt;/span&gt; &lt;span class="k"&gt;do&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;render&lt;/span&gt; &lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product: &lt;/span&gt;&lt;span class="vi"&gt;@product&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;p&gt;This will respond with a &lt;code&gt;turbo-stream&lt;/code&gt; that will look something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;turbo-stream&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"update"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"remote_modal_body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-stream&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will tell turbo to look for an html id &lt;code&gt;remote_modal_body&lt;/code&gt; (defined on the &lt;code&gt;remote_modal&lt;/code&gt; partial) and update its
content with whatever is inside the &lt;code&gt;template&lt;/code&gt; tag. In this case the form with
the rendered errors.&lt;/p&gt;

&lt;p&gt;Here we don&amp;rsquo;t actually need to respond with &lt;code&gt;:unprocessable_entity&lt;/code&gt; for the errors by rendered.
But I think that in this case it is a better response status than &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;If we try to compact the whole post in some steps&amp;hellip;&lt;/p&gt;

&lt;h3&gt;To show a remote modal you will need&amp;hellip;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A link with a &lt;code&gt;data-turbo-frame=&amp;quot;remote-modal&amp;quot;&lt;/code&gt; (or whatever id you want).&lt;/li&gt;
&lt;li&gt;An already defined &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote-modal&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A server response with a matching &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote-modal&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A stimulus controller to &amp;ldquo;show&amp;rdquo; the modal, once the content is there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;To handle redirects and close the modal you will need to&amp;hellip;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;target=&amp;quot;_top&amp;quot;&lt;/code&gt; to your already defined &lt;code&gt;&amp;lt;turbo-frame id=&amp;quot;remote-modal&amp;quot;&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Respond with a redirect from the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;To close the modal with the fade effect&amp;hellip;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Listen to the &lt;code&gt;turbo:before-render&lt;/code&gt; event and then&amp;hellip;&lt;/li&gt;
&lt;li&gt;Prevent the default behavior.&lt;/li&gt;
&lt;li&gt;Close the modal.&lt;/li&gt;
&lt;li&gt;Resume the render after the modal has been closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;And to render the errors&amp;hellip;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Respond with a turbo stream that updates the form html.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Using a form object to create more than one record</title>
    <link rel="alternate" href="https://bhserna.com/you-can-create-a-model-for-that-multi-record-form"/>
    <id>https://bhserna.com/you-can-create-a-model-for-that-multi-record-form</id>
    <published>2020-10-14T22:54:00Z</published>
    <updated>2020-10-14T22:54:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you been stuck trying to build a form to save more than one record?&lt;/p&gt;

&lt;p&gt;Rails form helpers are really easy to work with when you are working with an
&lt;code&gt;ActiveRecord::Base&lt;/code&gt; object&amp;hellip;&lt;/p&gt;

&lt;p&gt;But what do you do when you need to create a form to save multiple records?…
How do you specify the &lt;code&gt;form_with&lt;/code&gt; code and fields?… How do you populate the
form options?&lt;/p&gt;

&lt;p&gt;One way of doing it is create a new object, what is sometimes called a &amp;ldquo;form object&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;Here I will try to explain you how you can use a form object to create more
than one record using one form.&lt;/p&gt;

&lt;h2&gt;&amp;ldquo;Personal finances&amp;rdquo; demo app&lt;/h2&gt;

&lt;p&gt;I have build a kind of &amp;ldquo;personal finances&amp;rdquo; app , to keep track of transactions in bank accounts.&lt;/p&gt;

&lt;p&gt;It has two records, &lt;code&gt;Account&lt;/code&gt; and &lt;code&gt;Transaction&lt;/code&gt;, and one of the features of
the app is that it can register a transfers from one account to another.&lt;/p&gt;

&lt;p&gt;The app provides a select for the target account and an input field for the
amount, with that it will register two transactions, one &amp;ldquo;outflow&amp;rdquo; from the
&amp;ldquo;source account&amp;rdquo; and one &amp;ldquo;inflow&amp;rdquo;, to the &amp;ldquo;target account&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;Like in the next video&amp;hellip;&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 62.5%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/13ef2cc1fd674648b44cc3d57c0c5037" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;p&gt;In this post I will show you how using a &amp;ldquo;form object&amp;rdquo;, can help you to&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specify the fields of the form&lt;/li&gt;
&lt;li&gt;Display errors&lt;/li&gt;
&lt;li&gt;Populate the &amp;ldquo;target account&amp;rdquo; options&lt;/li&gt;
&lt;li&gt;Process the received params&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Play with the code&lt;/h2&gt;

&lt;p&gt;The code of the app is on &lt;a href="https://github.com/bhserna/form_object_example"&gt;github.com/bhserna/form object_example&lt;/a&gt;, you can clone it and play with it.&lt;/p&gt;

&lt;h2&gt;Specify the fields of the form&lt;/h2&gt;

&lt;p&gt;If you go to the file of the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/models/transfer.rb"&gt;transfer
object&lt;/a&gt;
you will see that it &lt;code&gt;include ActiveModel::Model&lt;/code&gt; and defines some &amp;ldquo;attribute accessors&amp;rdquo;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transfer&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="kp"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:source_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
  &lt;span class="n"&gt;validates_presence_of&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:source_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You don&amp;rsquo;t really need &lt;code&gt;ActiveModel::Model&lt;/code&gt; to build a &amp;ldquo;form object&amp;rdquo; but it will
help you to handle the attributes initialization and validations like in
&lt;code&gt;ActiveRecord::Base&lt;/code&gt; objects.&lt;/p&gt;

&lt;p&gt;It initializes this object in the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/controllers/transfers_controller.rb"&gt;transfers controller&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransfersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;set_account&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="vi"&gt;@transfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;source_account_id: &lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;p&gt;And it uses it in the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/views/transfers/new.html.haml"&gt;form&lt;/a&gt;&amp;hellip; &lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;account_transfers_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:source_account_id&lt;/span&gt;
  &lt;span class="nt"&gt;%p&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;
    &lt;span class="nt"&gt;%br&lt;/span&gt;
    &lt;span class="nc"&gt;.date-inputs&lt;/span&gt;
      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date_select&lt;/span&gt; &lt;span class="ss"&gt;:date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;selected: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
  &lt;span class="nt"&gt;%p&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;
    &lt;span class="nt"&gt;%br&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_select&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;target_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
  &lt;span class="nt"&gt;%p&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
    &lt;span class="nt"&gt;%br&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number_field&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors_for&lt;/span&gt; &lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
  &lt;span class="nt"&gt;%p&lt;/span&gt;
    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the form uses the instance of our &lt;code&gt;Transfer&lt;/code&gt; object to populate each field as with regular &lt;code&gt;ActiveRecord::Base&lt;/code&gt; objects.&lt;/p&gt;

&lt;h2&gt;Display errors&lt;/h2&gt;

&lt;p&gt;If you see the
&lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/views/transfers/new.html.haml"&gt;form&lt;/a&gt;
, you will see that, it uses an &lt;code&gt;errors_for&lt;/code&gt; helper&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nt"&gt;%p&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
  &lt;span class="nt"&gt;%br&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number_field&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors_for&lt;/span&gt; &lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is defined in the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/helpers/application_helper.rb"&gt;application helper&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;errors_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;div&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It uses the &lt;a href="https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag"&gt;tag&lt;/a&gt; method from &lt;code&gt;ActionView&lt;/code&gt; and the &lt;a href="https://api.rubyonrails.org/classes/ActiveModel/Errors.html"&gt;errors&lt;/a&gt; object provided by &lt;code&gt;ActiveModel::Model&lt;/code&gt; in the &lt;code&gt;Transfer&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;Populate the &amp;ldquo;target account&amp;rdquo; options&lt;/h2&gt;

&lt;p&gt;If you see the
&lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/views/transfers/new.html.haml"&gt;form&lt;/a&gt;
again, you will see that for the &lt;code&gt;collection_select&lt;/code&gt; it is using &lt;code&gt;@transfer.target_options&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collection_select&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;target_options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can actually define this method wherever you want. Maybe the &lt;code&gt;Account&lt;/code&gt;
model could also be a good place. But as I am using this object specifically to
handle transfers I think that maybe this could be a good place to put this
behavior.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transfer&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;target_options&lt;/span&gt;
    &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;source_account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Process the received params&lt;/h2&gt;

&lt;p&gt;On the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/controllers/transfers_controller.rb"&gt;TransfersController&lt;/a&gt;&amp;hellip;&lt;/p&gt;

&lt;p&gt;It initializes the &lt;code&gt;Transfer&lt;/code&gt; with the &lt;code&gt;transfer_params&lt;/code&gt; and then it calls &lt;code&gt;@transfer.save&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@transfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transfer_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, It looks almost like an standard scaffold controller!&lt;/p&gt;

&lt;h3&gt;Constructing the transfer_params&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;ActiveRecord::Base&lt;/code&gt; object will let us pass directly the params that our
&lt;code&gt;date_select&lt;/code&gt; returns, but an &lt;code&gt;ActiveModel::Model&lt;/code&gt; can&amp;rsquo;t do that (at least for now).&lt;/p&gt;

&lt;p&gt;For that reason to build the
&lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/controllers/transfers_controller.rb#L20"&gt;transfer_params&lt;/a&gt;
it first builds the date from the params and then it merges it to the permitted
params.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transfer_params&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:transfer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:source_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:target_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="n"&gt;date_from_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:transfer&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;p&gt;You can find the implementation for the &lt;code&gt;date_from_params&lt;/code&gt; method on the &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/controllers/application_controller.rb#L8"&gt;ApplicationController&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It looks like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;date_from_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Date&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;date_args_from_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;date_args_from_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="sx"&gt;%w(1 2 3)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"date(&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;i)"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_i&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;h3&gt;Normalizing values&lt;/h3&gt;

&lt;p&gt;On the initialization first it
&lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/models/transfer.rb#L21"&gt;transforms&lt;/a&gt;
the &lt;code&gt;amount&lt;/code&gt; from an string to an integer (I am not using cents, because it is
a demo, in real life you may want to use a &lt;code&gt;money&lt;/code&gt; object or &lt;code&gt;BigDecimal&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Saving the form&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/bhserna/form_object_example/blob/main/app/models/transfer.rb#L10"&gt;Transfer#save&lt;/a&gt; method then checks if it is valid and then makes the &lt;code&gt;transfer&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;valid?&lt;/span&gt;
    &lt;span class="no"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;source_account: &lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_account_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;target_account: &lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_account_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, it using the &lt;code&gt;valid?&lt;/code&gt; method that will executes the defined validations, provided by &lt;code&gt;ActiveModel::Model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Transaction.transfer&lt;/code&gt; method is implemented on &lt;code&gt;Transaction&lt;/code&gt;, just because
I think that it makes more scence there. But depending on the complexity of
your problem you can execute the creation of the records directly on the form
or delegate it to another object,&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&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="c1"&gt;#...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;source_account&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;target_account&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;account: &lt;/span&gt;&lt;span class="n"&gt;source_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;target_name: &lt;/span&gt;&lt;span class="n"&gt;target_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Transfer to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;target_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;account: &lt;/span&gt;&lt;span class="n"&gt;target_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;target_name: &lt;/span&gt;&lt;span class="n"&gt;source_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Transfer from &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;source_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;Cool I think that&amp;rsquo;s it!!&lt;/p&gt;

&lt;p&gt;As you can see, you can use a form object when you need to create more than one record by using just one form to capture the data. It can help you to&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specify the fields of the form&lt;/li&gt;
&lt;li&gt;Display errors&lt;/li&gt;
&lt;li&gt;Compute custom data for the form, like select options&lt;/li&gt;
&lt;li&gt;Process the received params&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember that for now if your form uses &lt;code&gt;date_select&lt;/code&gt; or &lt;code&gt;datetime_select&lt;/code&gt; you will have to build the date from the params before passing it to your form object.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Dynamic filters with rails and hotwire</title>
    <link rel="alternate" href="https://bhserna.com/dynamic-filters-with-rails-and-hotwire"/>
    <id>https://bhserna.com/dynamic-filters-with-rails-and-hotwire</id>
    <published>2021-12-14T00:40:00Z</published>
    <updated>2021-12-14T00:40:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;A very common task as a Rails developer is to let the user filter a list with a
combination of search fields and selects.&lt;/p&gt;

&lt;p&gt;Here I want to show you one way of doing it using turbo and stimulus.js from
hotwire, with an example app.&lt;/p&gt;

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

&lt;h2&gt;Play with the code&lt;/h2&gt;

&lt;p&gt;The code of the app is on
&lt;a href="https://github.com/bhserna/dynamic_filters_hotwire"&gt;github.com/bhserna/dynamic filters hotwire&lt;/a&gt;,
you can clone it and play with it.&lt;/p&gt;

&lt;h2&gt;What does the app do?&lt;/h2&gt;

&lt;p&gt;The app displays a paginated list of products and lets the user filter the
products by selecting a category and typing a search term.&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 60.70826306913997%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/76e7527ec19b4f678303c7352d4d4b0d" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;How does it work?&lt;/h2&gt;

&lt;p&gt;By watching the file template &lt;a href="https://github.com/bhserna/dynamic_filters_hotwire/blob/main/app/views/products/index.haml"&gt;products/index.haml&lt;/a&gt;, you will see that the
index page is composed of the title and two partials, &lt;code&gt;filters&lt;/code&gt; and &lt;code&gt;table&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nt"&gt;%h1&lt;/span&gt; Products

&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"filters"&lt;/span&gt;
&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;products: &lt;/span&gt;&lt;span class="vi"&gt;@products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;pagy: &lt;/span&gt;&lt;span class="vi"&gt;@pagy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is not necessary to break the html in this two partials but I like it that way 😅.
It helps me to understand the code.&lt;/p&gt;

&lt;p&gt;The &amp;ldquo;special&amp;rdquo; thing of the app is that it uses &lt;code&gt;turbo&lt;/code&gt; and &lt;code&gt;stimulus.js&lt;/code&gt; to
update the content when of the table when you select a new category or if you
type in the search field.&lt;/p&gt;

&lt;h2&gt;The filters form&lt;/h2&gt;

&lt;p&gt;If you go to the &lt;a href="https://github.com/bhserna/dynamic_filters_hotwire/blob/main/app/views/products/_filters.haml"&gt;products/filters&lt;/a&gt; partial you will see that the form defines three important data attributes&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data-controller=&amp;quot;filters&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-filters-target=&amp;quot;form&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-turbo-frame=&amp;quot;table&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;products_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"filters-container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;method: :get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;controller: &lt;/span&gt;&lt;span class="s2"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;filters_target: &lt;/span&gt;&lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;turbo_frame: &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;data-controller=&amp;quot;filters&amp;quot;&lt;/code&gt; tells stimulus.js to create a instance of the controller class in
&lt;code&gt;filters_controller.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data-filters-target=&amp;quot;form&amp;quot;&lt;/code&gt; marks the form as a &lt;code&gt;formTarget&lt;/code&gt; in the &lt;code&gt;filters_controller.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data-turbo-frame=&amp;quot;table&lt;/code&gt; tells turbo that you want to update &lt;code&gt;turbo-frame&lt;/code&gt; with the id &lt;code&gt;table&lt;/code&gt;. If
you don&amp;rsquo;t set this, turbo will change the whole page and if the user is typing
in the search input it will lose focus.&lt;/p&gt;

&lt;h2&gt;The inputs&lt;/h2&gt;

&lt;p&gt;In the same form the select and the input&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nc"&gt;.filters-group&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Filter by category"&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options_for_select&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;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:category&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;include_blank: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s2"&gt;"filters#submit"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.filters-group&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Search"&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;value: &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;:search&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;action: &lt;/span&gt;&lt;span class="s2"&gt;"filters#submit"&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;p&gt;&amp;hellip; both have a &lt;code&gt;data-action=&amp;quot;filters#submit&amp;quot;&lt;/code&gt; that will call the &lt;code&gt;submit()&lt;/code&gt;
function on the &lt;code&gt;filters_controller.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"@hotwired/stimulus"&lt;/span&gt;

&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"form"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestSubmit&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;To submit the form we need to &lt;a href="https://discuss.hotwired.dev/t/triggering-turbo-frame-with-js/1622/12"&gt;actually trigger the
submit&lt;/a&gt;
event that&amp;rsquo;s why need to call &lt;code&gt;requestSubmit()&lt;/code&gt; instead of &lt;code&gt;submit()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;The table partial&lt;/h2&gt;

&lt;p&gt;Now if you go to the &lt;a href="https://github.com/bhserna/dynamic_filters_hotwire/blob/main/app/views/products/_table.haml"&gt;products/table&lt;/a&gt; you will find that the first line wraps
the rest of the content in a &lt;code&gt;turbo_frame_tag&lt;/code&gt; with an &lt;code&gt;id&lt;/code&gt; &lt;code&gt;&amp;quot;table&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="nc"&gt;.loading-container&lt;/span&gt;
    &lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="nc"&gt;.loading-element&lt;/span&gt; Loading...
  &lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="nc"&gt;.table-container&lt;/span&gt;
    &lt;span class="nt"&gt;%table&lt;/span&gt;
      &lt;span class="nt"&gt;%thead&lt;/span&gt;
        &lt;span class="nt"&gt;%tr&lt;/span&gt;
          &lt;span class="nt"&gt;%th&lt;/span&gt; Name
          &lt;span class="nt"&gt;%th&lt;/span&gt; Category
          &lt;span class="nt"&gt;%th&lt;/span&gt; Price
      &lt;span class="nt"&gt;%tbody&lt;/span&gt;
        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="n"&gt;pagy_nav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@pagy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, if you watch the &lt;code&gt;html&lt;/code&gt; output you will see&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"table"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  # rest of the content...
&lt;span class="nt"&gt;&amp;lt;/turbo-frame&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This tag matches the &lt;code&gt;data-turbo-frame=&amp;quot;table&amp;quot;&lt;/code&gt; from the form, and now &lt;code&gt;turbo&lt;/code&gt;
knows that it should update this &amp;ldquo;turbo frame&amp;rdquo; with the server response when
the form is submitted.&lt;/p&gt;

&lt;h2&gt;The loading indicator&lt;/h2&gt;

&lt;p&gt;If you wathch the video again, you will see that there is a little &amp;ldquo;Loading&amp;hellip;&amp;rdquo;
that is shown when the form changes.&lt;/p&gt;

&lt;p&gt;The html that renders this indicator is on the
&lt;a href="https://github.com/bhserna/dynamic_filters_hotwire/blob/main/app/views/products/_table.haml"&gt;products/table&lt;/a&gt;
partial, specifically this part&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"table"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="nc"&gt;.loading-container&lt;/span&gt;
    &lt;span class="nt"&gt;%div&lt;/span&gt;&lt;span class="nc"&gt;.loading-element&lt;/span&gt; Loading...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The css for the class &lt;code&gt;.loading-element&lt;/code&gt; is defined on the &lt;code&gt;application.css&lt;/code&gt; as&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nt"&gt;turbo-frame&lt;/span&gt; &lt;span class="nc"&gt;.loading-element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;turbo-frame&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;busy&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.loading-element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&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;p&gt;This works because &lt;code&gt;busy&lt;/code&gt; is a boolean attribute managed by turbo. It is toggled to
be present when a &lt;code&gt;turbo-frame&lt;/code&gt;-initiated request starts, and toggled false
when the request ends&lt;/p&gt;

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

&lt;p&gt;As you can see with very little &amp;ldquo;special code&amp;rdquo; hotwire can help us to achieve
this very common task.&lt;/p&gt;

&lt;p&gt;What you need to make this work is to set &lt;code&gt;data-turbo-frame=&amp;quot;table&amp;quot;&lt;/code&gt; on a form
and have a &lt;code&gt;turbo_frame_tag &amp;quot;table&amp;quot;&lt;/code&gt; on the same page, and call
&lt;code&gt;requestSubmit()&lt;/code&gt; when you want to submit the form.&lt;/p&gt;

&lt;p&gt;If you want a loading indicator, you can use the &lt;code&gt;turbo_frame[busy]&lt;/code&gt; attribute
to display the indicator when needed.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Styler, a tool to compose css classes with ruby</title>
    <link rel="alternate" href="https://bhserna.com/styler-a-tool-to-compose-css-classes-with-ruby"/>
    <id>https://bhserna.com/styler-a-tool-to-compose-css-classes-with-ruby</id>
    <published>2021-12-07T12:36:00Z</published>
    <updated>2021-12-07T12:36:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;One of the things that I want from css is to have to possibility to compose
already defined styles, to define new ones&amp;hellip;&lt;/p&gt;

&lt;p&gt;If you try to write &amp;ldquo;Semantic CSS&amp;rdquo;, you will find a hard time trying to avoid
the repetition on things that look the same but are different things, like
when you want to render a &amp;ldquo;card&amp;rdquo; for the an author and then for an article.&lt;/p&gt;

&lt;p&gt;You can create &amp;ldquo;content agnostic CSS components&amp;rdquo;, but things start
to get complicated, when you want to avoid duplication if things from one
component are similar to other components.&lt;/p&gt;

&lt;p&gt;One way of solving this problems is by using something like the &lt;code&gt;@extend&lt;/code&gt;
function from sass, or the &lt;code&gt;@apply&lt;/code&gt; function from Tailwind css, but both tools
are recommended just for some specific cases and not to build your styles on
top of them (&lt;a href="https://csswizardry.com/2014/11/when-to-use-extend-when-to-use-a-mixin/"&gt;@extend&lt;/a&gt;, &lt;a href="https://twitter.com/adamwathan/status/1226511611592085504"&gt;@apply&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;A tool to compose css classes&lt;/h2&gt;

&lt;p&gt;This is why I have been working on this
&lt;a href="https://github.com/bhserna/styler/"&gt;Styler&lt;/a&gt;, a tool to compose css classes
from other classes an groups of classes.&lt;/p&gt;

&lt;p&gt;Is a tool, to extend what the utility css classes can give us. A tool to build
on top of frameworks like Tachyons or Tailwind css.&lt;/p&gt;

&lt;h2&gt;Define styles&lt;/h2&gt;

&lt;p&gt;You can declare styles with a name and an already defined css class, for example if you define a &lt;code&gt;style&lt;/code&gt;
named &lt;code&gt;btn&lt;/code&gt;, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&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;p&gt;That will return an object where you can call &lt;code&gt;btn&lt;/code&gt; on it&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "white bg_blue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You would be able to use it like this in your erb files&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;btn&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;My button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;or in haml&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nt"&gt;%button&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; My button
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To output&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"white bg_blue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;My button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Compose styles&lt;/h2&gt;

&lt;p&gt;You can define many of this styles and compose them&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"padding3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"marin3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;blue_btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&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;pre&gt;&lt;code class="highlight haml"&gt;&lt;span class="nt"&gt;%button&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; My button
&lt;span class="nt"&gt;%button&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blue_btn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; My button
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the output would be&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight html"&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"padding3 margin3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;My button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"padding3 margin3 white bg_blue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;My button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Substract styles&lt;/h2&gt;

&lt;p&gt;By composing your styles you can also substract classes from previous styles, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bg_red"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 white bg_blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 white bg_red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Passing arguments to styles&lt;/h2&gt;

&lt;p&gt;You can also define styles that expect an argument, to help you decide which styles to display, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default_color&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;project&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_red"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "bg_blue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you can use this styles to build other styles, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default_color&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;project&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_red"&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="n"&gt;style&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "bg_blue pa3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default_color&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;project&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bg_red"&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="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;title&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;project&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"pa3"&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="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "bg_blue pa3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Define collections&lt;/h2&gt;

&lt;p&gt;You can define collections as namespaces for your styles&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&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="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:danger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Nested collections&lt;/h2&gt;

&lt;p&gt;You can define nested collections to build complete themes&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&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="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Define collection with arguments&lt;/h2&gt;

&lt;p&gt;Like with the styles, you can define collections that require arguments, like this..&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&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;project&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;color: &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Select a collection with an alias&lt;/h2&gt;

&lt;p&gt;And you can pick one of those collections, by using a &lt;code&gt;collection_alias&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&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="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&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="n"&gt;collection_alias&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Collection alias with a block&lt;/h2&gt;

&lt;p&gt;If you need you can select the collection alias dynamically with a block&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&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="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&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="n"&gt;collection_alias&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;theme&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;current_version&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_version&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"v1"&lt;/span&gt;
      &lt;span class="n"&gt;v1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;v2&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"v2"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Select a collection from other styler&lt;/h2&gt;

&lt;p&gt;You can also define your collections on different &amp;ldquo;stylers&amp;rdquo; and be able to
declare an alias to access them&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&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="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&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="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection_alias&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;theme&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;current_version&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_version&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"v1"&lt;/span&gt;
      &lt;span class="n"&gt;v1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;v2&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 blue"&lt;/span&gt;
&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"v2"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "pa3 red"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Copy styles from collection&lt;/h2&gt;

&lt;p&gt;If you need to use the styles from other collection, but you need to override
some of them, you can copy the styles and then override what you want.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Styler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pa3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"red"&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="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;buttons&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;copy_styles_from&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;
      &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;danger&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"red"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"orange"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&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;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"pa3 blue"&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;danger&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;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"pa3 orange"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Read the source, Luke&lt;/h2&gt;

&lt;p&gt;If you want to learn more you can read the source on
&lt;a href="https://github.com/bhserna/styler/"&gt;github&lt;/a&gt;, and maybe take a look a the
&lt;a href="https://github.com/bhserna/styler/tree/main/spec"&gt;specs&lt;/a&gt; to find more
examples.&lt;/p&gt;

&lt;h2&gt;Library status&amp;hellip; It would be nice to receive some feedback.&lt;/h2&gt;

&lt;p&gt;I have been working on this tool for some weeks, and I have been using it on
some personal projects and to build a collection with the &lt;a href="2022-01-28-styler-tachyons-examples.html.markdown"&gt;tachyons
components&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have pitched the library to my team at &lt;a href="https://www.briq.mx"&gt;briq.mx&lt;/a&gt; (we use
&lt;a href="https://tachyons.io"&gt;Tachyons&lt;/a&gt;), but we
are not using it yet&amp;hellip;&lt;/p&gt;

&lt;p&gt;Is still a concept, and would be nice to receive some feedback from you!&lt;/p&gt;

&lt;h2&gt;Installation&lt;/h2&gt;

&lt;p&gt;If you want to play with this tool in a project you can install it by adding
this line to your application&amp;rsquo;s Gemfile:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"ruby_styler"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;$ bundle install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or install it yourself as:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;$ gem install styler
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Capybara cheatsheet as pdf</title>
    <link rel="alternate" href="https://bhserna.com/capybara-cheatsheet-as-pdf"/>
    <id>https://bhserna.com/capybara-cheatsheet-as-pdf</id>
    <published>2021-11-29T13:27:00Z</published>
    <updated>2021-11-29T13:27:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;If you work with capybara and you are constantly searching for capybara helpers, maybe to have this little cheatsheet at hand could work for you.&lt;/p&gt;

&lt;p&gt;I already have share it on as &lt;a href="/capybara-cheatsheet.html"&gt;a blog post&lt;/a&gt;, but now I want to share it with you as a pdf.&lt;/p&gt;

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

&lt;p&gt;&lt;script async data-uid="9b5d718a7e" src="https://bhserna.ck.page/9b5d718a7e/index.js"&gt;&lt;/script&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Preloading associations cheatsheet</title>
    <link rel="alternate" href="https://bhserna.com/preloading-associations-cheatsheet"/>
    <id>https://bhserna.com/preloading-associations-cheatsheet</id>
    <published>2021-11-19T12:32:00Z</published>
    <updated>2021-11-19T12:32:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe you are already familiar with &lt;code&gt;includes&lt;/code&gt; or &lt;code&gt;preload&lt;/code&gt;, but you know that a lot of the time you will need more than just &lt;code&gt;preload(:comments)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I have already share with you a &lt;a href="https://bhserna.com/guide-for-preloading-associations-in-rails.html"&gt;guide for preloading associations&lt;/a&gt;&amp;hellip;&lt;/p&gt;

&lt;p&gt;It starts with the basics, with just a regular &lt;code&gt;has_many&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; or&lt;code&gt;includes&lt;/code&gt;, and build from this to then show you things like…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to preload nested associations&lt;/li&gt;
&lt;li&gt;How to define associations to help you simplify the preloading&lt;/li&gt;
&lt;li&gt;How to preload just a part of the association like an scope&lt;/li&gt;
&lt;li&gt;How to preload just the “top 1 per group” or the “latest of each”&lt;/li&gt;
&lt;li&gt;How to preload just the “top n per group” or the “latest n of each”&lt;/li&gt;
&lt;li&gt;How to use custom objects to represent a preloading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I want to share with you a little cheatsheet that covers the same but in a condensed way.&lt;/p&gt;

&lt;p&gt;It could be useful as a first read of the content or as a tool to remember hints of the different topics or methods.&lt;/p&gt;

&lt;p&gt;&lt;script async data-uid="73c5a7df66" src="https://bhserna.ck.page/73c5a7df66/index.js"&gt;&lt;/script&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to detect n+1 queries by watching the logs</title>
    <link rel="alternate" href="https://bhserna.com/detect-n-plus-one-queries-by-watching-the-logs"/>
    <id>https://bhserna.com/detect-n-plus-one-queries-by-watching-the-logs</id>
    <published>2021-11-16T12:56:00Z</published>
    <updated>2021-11-16T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Although there are &lt;a href="https://bhserna.com/tools-to-help-you-detect-n-1-queries.html#prosopite"&gt;tools that can help you detect n+1&lt;/a&gt; queries before they hit production, I think that is good to be able to identify n+1 queries directly by watching your logs&amp;hellip;&lt;/p&gt;

&lt;p&gt;Not all the tools will tell you exactly that you have n+1 queries, and sometimes the ones that will tell you, can give you false negatives.&lt;/p&gt;

&lt;p&gt;Also I think that, although there is hardly no training anywhere for this ability, is always an expected ability for an experienced rails developer.&lt;/p&gt;

&lt;h2&gt;Look for repeated queries&lt;/h2&gt;

&lt;p&gt;As you may know, an &lt;a href="what-is-an-n-plus-1-queries-problem.html"&gt;n+1 queries problem means&lt;/a&gt; that a query is executed for every result of a previous query.&lt;/p&gt;

&lt;p&gt;For example, given a &lt;code&gt;Post&lt;/code&gt; that has many comments, If you execute the next code…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&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;:comments&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;:to_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will execute…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query to fetch the posts (&lt;code&gt;Post.all&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;And &lt;code&gt;n&lt;/code&gt; queries to fetch the &lt;code&gt;comments&lt;/code&gt; of each &lt;code&gt;post&lt;/code&gt;(&lt;code&gt;post.comments&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, if you go to the console and test this code&amp;hellip;&lt;/p&gt;

&lt;p&gt;If there are 2 posts, it will execute 3 queries (2 + 1), and you will see in your logs, something like&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;&amp;gt; Post.first(2).map(&amp;amp;:comments).map(&amp;amp;:to_a)
Post Load (0.8ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 2]]
Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 1]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 2]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if there are 5 posts, it will execute 6 queries (5 + 1), and you will see in your logs, something like&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;&amp;gt; Post.first(5).map(&amp;amp;:comments).map(&amp;amp;:to_a)
Post Load (0.8ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 5]]
Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 1]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 2]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 3]]
Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 4]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 5]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if there are 1000 posts, it will execute 1001 queries (1000 + 1), and in the same way you will see in your logs&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;&amp;gt; Post.first(1000).map(&amp;amp;:comments).map(&amp;amp;:to_a)
Post Load (0.8ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1000]]
Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 1]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 2]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 3]]
Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 4]]
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 5]]
#...
Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC  [["post_id", 1000]]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Look for repeated queries executed in the same place&lt;/h2&gt;

&lt;p&gt;The previous examples show examples taken from the rails console, but as you can see in the next example&amp;hellip;&lt;/p&gt;

&lt;p&gt;When you start the server, on your logs, after each query, rails will show you where is the code that executes that query.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Started GET "/posts" for ::1 at 2021-11-10 06:47:40 -0600
Processing by PostsController#index as HTML
  Rendering layout layouts/application.html.erb
  Rendering posts/index.html.erb within layouts/application
  Rendered posts/_filters.html.erb (Duration: 6.5ms | Allocations: 2354)
  Post Load (1.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."comments_count" = $1  [["comments_count", 5]]
  ↳ app/views/posts/index.html.erb:5
  Comment Load (3.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" DESC LIMIT $2  [["post_id", 1], ["LIMIT", 1]]
  ↳ app/views/posts/_post.html.erb:6
  Comment Load (1.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" DESC LIMIT $2  [["post_id", 2], ["LIMIT", 1]]
  ↳ app/views/posts/_post.html.erb:6
  Comment Load (1.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" DESC LIMIT $2  [["post_id", 3], ["LIMIT", 1]]
  ↳ app/views/posts/_post.html.erb:6
  Comment Load (1.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" DESC LIMIT $2  [["post_id", 4], ["LIMIT", 1]]
  ↳ app/views/posts/_post.html.erb:6
  Comment Load (1.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" DESC LIMIT $2  [["post_id", 5], ["LIMIT", 1]]
  ↳ app/views/posts/_post.html.erb:6
  #...
  Rendered collection of posts/_post.html.erb [20 times] (Duration: 84.5ms | Allocations: 34947)
  Rendered posts/index.html.erb within layouts/application (Duration: 107.3ms | Allocations: 45186)
  Rendered layout layouts/application.html.erb (Duration: 175.0ms | Allocations: 77162)
Completed 200 OK in 204ms (Views: 147.6ms | ActiveRecord: 34.7ms | Allocations: 86545)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, if you look out for this two things, &lt;strong&gt;repeated queries that are executed from the sample place&lt;/strong&gt; &amp;hellip; you can be almost sure that you have an n+1 queries problem in that place.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How can I determine if my n+1 queries fix can hurt the performance?</title>
    <link rel="alternate" href="https://bhserna.com/determine-if-my-n-plus-one-queries-can-hurt-performance"/>
    <id>https://bhserna.com/determine-if-my-n-plus-one-queries-can-hurt-performance</id>
    <published>2021-11-02T12:56:00Z</published>
    <updated>2021-11-02T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe you already know that &lt;a href="fixing-n-plus-1-queries-can-hurt-performance.html"&gt;&amp;ldquo;fixing&amp;rdquo; an n+1 queries problem can hurt the performance of your app&amp;hellip;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But even if you already know it and also understand how it can happen, it could not be obvious how to know if this is will happen with your fix.&lt;/p&gt;

&lt;p&gt;Some things that you can try are to&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compare the requests time&lt;/li&gt;
&lt;li&gt;Compare the queries load time&lt;/li&gt;
&lt;li&gt;&lt;a href="fixing-n-plus-one-queries-can-hurt-performance-example-app.html#compare_allocations"&gt;Compare the produced allocations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on the criticality of the fix and the volume that is handling your app you can just push to production and check there or try to simulate with the expected volume of data on development.&lt;/p&gt;

&lt;h2&gt;Try on development first&lt;/h2&gt;

&lt;p&gt;Although sometimes there will be no problem if you just push to production, if your intuition tells you that maybe the associations that you are preloading will preload to much data, you can try to simulate the expected volume of data that you will expect on production. This will help you to have more control on your tests.&lt;/p&gt;

&lt;h2&gt;Where to find the information?&lt;/h2&gt;

&lt;p&gt;You can use different tools like rack-mini-profiler or tools on your production application, like NewRelic or Skylight, but you can also take a look on your logs.&lt;/p&gt;

&lt;p&gt;If you looks in your logs, for each request you will find something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Started GET "/posts" for ::1 at 2021-10-05 18:11:18 -0500
Processing by PostsController#index as HTML
  Rendering layout layouts/application.html.erb
  Rendering posts/index.html.erb within layouts/application
  Post Load (4.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."comments_count" = $1  [["comments_count", 5]]
  ↳ app/views/posts/index.html.erb:35
  Comment Load (4.8ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ORDER BY "comments"."id" ASC  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5], ["post_id", 6], ["post_id", 7], ["post_id", 8], ["post_id", 9], ["post_id", 10], ["post_id", 11], ["post_id" 12], ["post_id", 13], ["post_id", 14], ["post_id", 15], ["post_id", 16], ["post_id", 17], ["post_id", 18], ["post_id", 19], ["post_id", 20]]
  ↳ app/views/posts/index.html.erb:35
  Rendered posts/index.html.erb within layouts/application (Duration: 58.0ms | Allocations: 18269)
  Rendered layout layouts/application.html.erb (Duration: 64.0ms | Allocations: 18907)
Completed 200 OK in 92ms (Views: 50.9ms | ActiveRecord: 23.8ms | Allocations: 24737)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There, for the sql you will find&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The sql used for the query&lt;/li&gt;
&lt;li&gt;The query load time&lt;/li&gt;
&lt;li&gt;And the place where the query was called in your code&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Post Load (4.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."comments_count" = $1  [["comments_count", 5]]
  ↳ app/views/posts/index.html.erb:35
  Comment Load (4.8ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) ORDER BY "comments"."id" ASC  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4], ["post_id", 5], ["post_id", 6], ["post_id", 7], ["post_id", 8], ["post_id", 9], ["post_id", 10], ["post_id", 11], ["post_id" 12], ["post_id", 13], ["post_id", 14], ["post_id", 15], ["post_id", 16], ["post_id", 17], ["post_id", 18], ["post_id", 19], ["post_id", 20]]
  ↳ app/views/posts/index.html.erb:35
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For each rendered file you will find&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The rendering time as &amp;ldquo;Duration&amp;rdquo;&lt;/li&gt;
&lt;li&gt;And the number of Allocations&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Rendered posts/index.html.erb within layouts/application (Duration: 58.0ms | Allocations: 18269)
Rendered layout layouts/application.html.erb (Duration: 64.0ms | Allocations: 18907)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And at the bottom you will find the total times&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request time&lt;/li&gt;
&lt;li&gt;The time spent in views&lt;/li&gt;
&lt;li&gt;The time spent in ActiveRecord&lt;/li&gt;
&lt;li&gt;And the time in Allocations&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Completed 200 OK in 92ms (Views: 50.9ms | ActiveRecord: 23.8ms | Allocations: 24737)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Practice app&lt;/h2&gt;

&lt;p&gt;I have built a little app to help you visualize &lt;a href="fixing-n-plus-1-queries-can-hurt-performance.html"&gt;why “fixing” an n+1 queries problem, can hurt the performance of your application&lt;/a&gt; if you preload associations with too many records.&lt;/p&gt;

&lt;p&gt;It simulates the index page of a blog application. It has the rack-mini-profiler gem, and it shows the logs in the page to help you visualize easily the queries, request time and allocations.&lt;/p&gt;

&lt;p&gt;By default it has an n+1 queries problem. It makes a call to the database to fetch the latest comment for each post in the list, but you can fix it with a checkbox.&lt;/p&gt;

&lt;p&gt;It will let you compare request times combining a filter of posts by number of comments and the checkbox to “fix” the n+1 queries problem.&lt;/p&gt;

&lt;p&gt;I explain more about it on this link: &lt;a href="fixing-n-plus-one-queries-can-hurt-performance-example-app.html"&gt;Example app to understand why some times fixing some n+1 queries can hurt performance&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Joins does not preload</title>
    <link rel="alternate" href="https://bhserna.com/joins-does-not-preload"/>
    <id>https://bhserna.com/joins-does-not-preload</id>
    <published>2021-10-24T12:56:00Z</published>
    <updated>2021-10-24T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When you are working with SQL, to use information from other tables you would
use a JOIN&amp;hellip;&lt;/p&gt;

&lt;p&gt;And if you are just starting to learn ActiveRecord comming from SQL, you could
think that by using the &lt;code&gt;joins&lt;/code&gt; method you will be able to later use the data
from the associations without doing a new query.&lt;/p&gt;

&lt;p&gt;And this is true and false at the same time&amp;hellip;&lt;/p&gt;

&lt;h2&gt;It will let you use the association on the query&lt;/h2&gt;

&lt;p&gt;Yes you will be able to use the data of the associations but just in the same
query. If you want to use the data from an association after the query result
has been returned, you will need a new query.&lt;/p&gt;

&lt;p&gt;To see it more clearly let&amp;rsquo;s try an example&amp;hellip;&lt;/p&gt;

&lt;p&gt;This will do an INNER JOIN:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And this will trigger a new database call for each post:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because joins does not preload the data.&lt;/p&gt;

&lt;p&gt;If you see the produced query you will see that what rails does, is to use the
association data to build the join to the comments table.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="o"&gt;*&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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_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;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With that join you will be able to use the associated relation (in this case
the &lt;code&gt;comments&lt;/code&gt; table) in the query, by chaining other methods from the &lt;a href="https://guides.rubyonrails.org/active_record_querying.html"&gt;query
interface&lt;/a&gt; like a
&lt;code&gt;where&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt;, &lt;code&gt;group&lt;/code&gt;&amp;hellip;&lt;/p&gt;

&lt;p&gt;For example&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"hola"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will build the SQL clause with a WHERE like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="o"&gt;*&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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_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;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'hola'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can do the same with other methods like &lt;code&gt;order&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;"comments.created_at DESC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will build the SQL clause with an ORDER like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="o"&gt;*&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;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post_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;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;To preload you will need another method&lt;/h2&gt;

&lt;p&gt;And if you really want to preload the comments you will need to use &lt;code&gt;preload&lt;/code&gt; ,
&lt;code&gt;includes&lt;/code&gt; or &lt;code&gt;eager_load&lt;/code&gt;, to actually tell ActiveRecord to also
fetch the associated records. For example:&lt;/p&gt;

&lt;p&gt;This will do an INNER JOIN and a also preload the comments:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"hola"&lt;/span&gt; &lt;span class="p"&gt;})&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;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now this will not trigger a new database call for each post:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;More about joins and methods to preload&lt;/h2&gt;

&lt;p&gt;If you want learn more about the difference between &lt;code&gt;joins&lt;/code&gt; and the three
method that you can use to preload associations &lt;code&gt;preload&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt; and
&lt;code&gt;eager_load&lt;/code&gt;. You can read this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bhserna.com/includes-preload-eager-load-joins-in-rails.html"&gt;What is the difference between includes, preload, eager_load and joins in ActiveRecord?&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Example app to understand why some times fixing some n+1 queries can hurt performance</title>
    <link rel="alternate" href="https://bhserna.com/fixing-n-plus-one-queries-can-hurt-performance-example-app"/>
    <id>https://bhserna.com/fixing-n-plus-one-queries-can-hurt-performance-example-app</id>
    <published>2021-10-12T12:56:00Z</published>
    <updated>2021-10-12T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I want to share with you an example application to help you visualize &lt;a href="/fixing-n-plus-1-queries-can-hurt-performance.html"&gt;why
&amp;ldquo;fixing&amp;rdquo; an n+1 queries problem, can hurt the performance of your
application&lt;/a&gt; if
you preload assocations with too many records.&lt;/p&gt;

&lt;p&gt;It simulates the index page of a blog application. And appart from the
rack-mini-profiler gem, it shows the logs in the page to help you visualize
easily the queries, request time and allocations.&lt;/p&gt;

&lt;p&gt;By default it has an n+1 queries problem. It makes a call to the database to
fetch the latest comment for each post in the list, but you can fix it with a
checkbox.&lt;/p&gt;

&lt;p&gt;It will let you compare request times combining a filter of posts by number of
comments and the checkbox to &amp;ldquo;fix&amp;rdquo; the n+1 queries problem.&lt;/p&gt;

&lt;div style="position: relative; padding-bottom: 60.70826306913997%; height: 0;"&gt;&lt;iframe src="https://www.loom.com/embed/6405c42e78d74ef29277a2930e2b3834" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;&lt;/div&gt;

&lt;h2&gt;Run the app&lt;/h2&gt;

&lt;p&gt;You can access the code on github, on &lt;a href="https://github.com/bhserna/why_n_plus_one_fix_hurts_performance"&gt;bhserna/why_n_plus_one_fix_hurts_performance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The app uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ruby-3.0.0&lt;/li&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use the app effectively you need to run the seeds.rb because it will create the posts that you need to test the app.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;seeds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="n"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_posts&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;(Don&amp;rsquo;t worry it is semi-fast because it uses &lt;code&gt;insert_all&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can update that file to test other combinations.&lt;/p&gt;

&lt;p&gt;To create and initialize the database for the first time you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;./bin/rails db:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to reset the database and run new seeds, you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;./bin/rails db:reset
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Fix n+1 queries&lt;/h2&gt;

&lt;p&gt;This is code to display each post&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&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="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&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="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;💬 &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;number_with_delimiter&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"comment"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Latest comment:&lt;span class="nt"&gt;&amp;lt;/strong&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&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;p&gt;As you can see&amp;hellip; if we don&amp;rsquo;t preload the comments, ActiveRecord will make a
new database call for each post in this line&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Latest comment:&lt;span class="nt"&gt;&amp;lt;/strong&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The checkbox with label &amp;ldquo;Fix n+1 queries?&amp;rdquo; will fix the n+1 queries doing
&lt;code&gt;preload(:comments)&lt;/code&gt; on the controller.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="n"&gt;current_filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fix_n_plus_one_queries?&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Filter the posts by number of comments&lt;/h2&gt;

&lt;p&gt;In the page we have a select field that will display just the posts with the selected number of
comments. We have posts with 5, 10, 50, 100, 1,000 and 10,000.&lt;/p&gt;

&lt;p&gt;For each &amp;ldquo;number of comments&amp;rdquo; option we have 20 posts.&lt;/p&gt;

&lt;p&gt;You can use this select to play with different combinations of number of
comments, with and without the fix.&lt;/p&gt;

&lt;h2&gt;Finding the request time and allocations&lt;/h2&gt;

&lt;p&gt;You can see the request time and allocations on the logs, if you look for something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Completed 200 OK in 92ms (Views: 50.9ms | ActiveRecord: 23.8ms | Allocations: 24737)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can also see query times:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Post Load (4.2ms) SELECT "posts".* FROM "posts"....
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And template rendering times:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Rendered posts/index.html.erb within layouts/application (Duration: 58.0ms | Allocations: 18269)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Compare the request time&lt;/h2&gt;

&lt;p&gt;If you compare different combiantions of number of comments, you will see that
&amp;ldquo;fixing&amp;rdquo; the n+1 queries problem, sometimes will produce faster responses, but
not always.&lt;/p&gt;

&lt;p&gt;And for some cases, even when it is faster it could not be enough
for your needs, and maybe is time to look for other ways to solve the problem.&lt;/p&gt;

&lt;h2 id="compare_allocations"&gt;Compare the allocations&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/nateberkopec"&gt;Nate Berkopec&lt;/a&gt; has a &lt;a href="https://twitter.com/nateberkopec/status/1442648442149367809?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1442648442149367809%7Ctwgr%5E%7Ctwcon%5Es1_&amp;amp;ref_url=http%3A%2F%2Flocalhost%3A4567%2Fwhich-n-plus-1-queries-should-stay-as-n-plus-1-queries.html"&gt;rule of thumb&lt;/a&gt;&amp;hellip;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&amp;lt;10,000&lt;/strong&gt; = don&amp;rsquo;t sweat it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10k-&amp;gt;100,000k&lt;/strong&gt; = might matter if you loop, but also don&amp;rsquo;t worry about it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;100k-&amp;gt;1mil&lt;/strong&gt; = reducing objects would speed this up considerably&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1mil-&amp;gt;10mil&lt;/strong&gt; = This is increasing your process&amp;rsquo; memory + is very slow&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&amp;gt;10mil&lt;/strong&gt; = ☠️&lt;/p&gt;

&lt;p&gt;You can play with the app and check the number of allocations that it is
producing for each request with differente combinations.&lt;/p&gt;

&lt;h2&gt;How to fix your n+1 queries fix&lt;/h2&gt;

&lt;p&gt;If are in the situation where your fix is not really a fix or that is not good
enough. There are several ways in that you can fix your problem.&lt;/p&gt;

&lt;p&gt;For a lot of this cases the first step is to implement a
&lt;a href="https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache"&gt;counter_cache&lt;/a&gt;
but when you really need to preload the data because you need to something more
than a counter, you can give a look to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;5 ways to fix the latest-comment n+1 problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html"&gt;5 ways to fetch the latest-N-of-each record on Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="guide-for-preloading-associations-in-rails.html"&gt;A guide for preloading associations in rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Why some people say that fixing some n+1 queries could hurt performance?</title>
    <link rel="alternate" href="https://bhserna.com/fixing-n-plus-1-queries-can-hurt-performance"/>
    <id>https://bhserna.com/fixing-n-plus-1-queries-can-hurt-performance</id>
    <published>2021-10-08T12:56:00Z</published>
    <updated>2021-10-08T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Sometimes people say that &amp;ldquo;fixing&amp;rdquo; some n+1 queries could hurt performance&amp;hellip;&lt;/p&gt;

&lt;p&gt;Maybe this phrase can be confusing, because if you have not been exposed to a lot of n+1
queries problems, it could be hard to imagine how it can be possible.&lt;/p&gt;

&lt;p&gt;And also is probably contrary to what you have always heard&amp;hellip;. That n+1
queries are bad.&lt;/p&gt;

&lt;h2&gt;You can preload too much data&lt;/h2&gt;

&lt;p&gt;Although a lot of the time a simple &lt;code&gt;preload(:comments)&lt;/code&gt; can solve your n+1
queries problems&amp;hellip; sometimes it can also hurt the performance if you don&amp;rsquo;t put
attention in the queries that ActiveRecord is using.&lt;/p&gt;

&lt;p&gt;Sometimes some of those queries &lt;strong&gt;will try to fetch a lot of data&lt;/strong&gt; and that
can make your queries slow or they can use to much memory and affect your
application in general.&lt;/p&gt;

&lt;p&gt;For example if you have a blog app with this model&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you preload all the comments for each post&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If your posts, would normally have just a few comments, like 5 to 100 comments,
everything will be fine&amp;hellip;.&lt;/p&gt;

&lt;p&gt;But if your posts are very popular, &lt;a href="benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;and have
between 5,000 and
10,000&lt;/a&gt; maybe
&amp;ldquo;just&amp;rdquo; preloading all the comments for each post could be a problem.&lt;/p&gt;

&lt;h2&gt;Be aware of associations with too many records&lt;/h2&gt;

&lt;p&gt;Maybe this example is not the most realistic example, but in general you need
to be aware if the association that you want to preload could have a lot of
records.&lt;/p&gt;

&lt;p&gt;Other examples could be&amp;hellip;&lt;/p&gt;

&lt;p&gt;In a &lt;strong&gt;chat app&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preloading all the &lt;code&gt;messages&lt;/code&gt; for all the &lt;code&gt;channels&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preloading all the &lt;code&gt;reads&lt;/code&gt; for the &lt;code&gt;messages&lt;/code&gt; on all the &lt;code&gt;channels&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preloading all the &lt;code&gt;reactions&lt;/code&gt; to the &lt;code&gt;messages&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an &lt;strong&gt;store or inventory system&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preload all the &lt;code&gt;part_numbers&lt;/code&gt; for a list of &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preload all the &lt;code&gt;page_views&lt;/code&gt; for a list of &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preload all the &lt;code&gt;likes&lt;/code&gt; for a list of &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a &lt;strong&gt;crowdfunding system&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preload all the &lt;code&gt;investments&lt;/code&gt; for all the &lt;code&gt;projects&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preload all the &lt;code&gt;payments&lt;/code&gt; for all the &lt;code&gt;investments&lt;/code&gt; for all &lt;code&gt;projects&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general not all associations will be a problem, but be aware if your
application have associations with too many records.&lt;/p&gt;

&lt;h2&gt;How to fix your n+1 queries fix&lt;/h2&gt;

&lt;p&gt;If your are in the situation where your fix was not really a fix or it is not
good enough. Here are some things that you can try&amp;hellip;&lt;/p&gt;

&lt;p&gt;First, of all for a lot of this cases the first step is to implement a
&lt;a href="https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache"&gt;counter_cache&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And when you really need to preload the data because you need something more
than a counter, you can give a look to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;5 ways to fix the latest-comment n+1 problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html"&gt;5 ways to fetch the latest-N-of-each record on Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="guide-for-preloading-associations-in-rails.html"&gt;A guide for preloading associations in rails&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Preload object on the guide for preloading associations</title>
    <link rel="alternate" href="https://bhserna.com/add-preload-object-to-guide-for-preloading-associations-in-rails"/>
    <id>https://bhserna.com/add-preload-object-to-guide-for-preloading-associations-in-rails</id>
    <published>2021-09-13T12:56:00Z</published>
    <updated>2021-09-13T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I want to tell you that I have added a new section to the &lt;a href="/guide-for-preloading-associations-in-rails.html"&gt;guide for preloading
associations in rails&lt;/a&gt;, to
introduce you to something I call &amp;ldquo;Preload objects&amp;rdquo; that will help you build
complex preloads for those cases when you can&amp;rsquo;t find a way to do what you need
to do, with the standard rails mechanisms.&lt;/p&gt;

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

&lt;h2&gt;Visit the new section&lt;/h2&gt;

&lt;p&gt;You can visit the new section on &lt;a href="guide-for-preloading-associations-in-rails.html#preload_object"&gt;Guide for preloading associations in rails - Preload
Object&lt;/a&gt;. And a standalone article about it on &lt;a href="/preload-object.html"&gt;Preload objects&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;If you don&amp;rsquo;t know what this &lt;a href="guide-for-preloading-associations-in-rails.html"&gt;guide&lt;/a&gt; is about&amp;hellip;&lt;/h2&gt;

&lt;p&gt;Is a guide I wrote to try to help you get better on this specific skill of
preloading associations, for when you need a more than just a
&lt;code&gt;preload(:comments)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To help you tackle problems like complex nested associations, scopes that you
need or want to reuse, places where preloading all the associated records could
hurt the performance of your app.&lt;/p&gt;

&lt;p&gt;I hope it can help you =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using an objet to represent a complex preload</title>
    <link rel="alternate" href="https://bhserna.com/preload-object"/>
    <id>https://bhserna.com/preload-object</id>
    <published>2021-08-23T12:56:00Z</published>
    <updated>2021-08-23T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Sometimes just using &lt;code&gt;preload&lt;/code&gt; or &lt;code&gt;includes&lt;/code&gt; is not possible&amp;hellip; or maybe it is, but you just can&amp;rsquo;t figure it out how to do it.&lt;/p&gt;

&lt;p&gt;Maybe you want to preload some records matching two keys, or preload a grouped
relation.&lt;/p&gt;

&lt;p&gt;Maybe you know how to represent the association with a &lt;code&gt;has_many
:through&lt;/code&gt;, but you need something faster, or to use less memory.&lt;/p&gt;

&lt;p&gt;In that kind of situations, one thing that you can do is to write a custom object to represent that preload.&lt;/p&gt;

&lt;p&gt;Here I will try to explain how and when you can do it with an example.&lt;/p&gt;

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

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

&lt;ul&gt;
  &lt;li&gt;&lt;a href="#example"&gt;Example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#has_many_through"&gt;Preload with has many :through&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#custom_query"&gt;Trying a custom query&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_object"&gt;A preload object&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#other_examples"&gt;Other examples&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#when"&gt;When should you use it?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="example"&gt;Example&lt;/h2&gt;

&lt;p&gt;For example, given the next model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment_votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: :voter_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Imagine that you want to fetch the people that have a vote on a comment to a post, the &amp;ldquo;comment voters&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;To implement it, you can have a method in the post to do that…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;comment_voters&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;votes: :voter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:voter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&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;p&gt;And it will correctly preload the &lt;code&gt;votes&lt;/code&gt; and &lt;code&gt;voters&lt;/code&gt; to compute the result.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&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;comment_voters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But if you try to do it for a list of post, it will execute n+1 queries&amp;hellip; What could you do? &lt;/p&gt;

&lt;h2 id="has_many_through"&gt;Preload with has_many :through&lt;/h2&gt;

&lt;p&gt;In this particular case you can try to re-shape your associations to solve it with a &lt;code&gt;has_many :through&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can add a &lt;code&gt;has_many :voters&lt;/code&gt; to &lt;code&gt;Comment&lt;/code&gt; and a &lt;code&gt;has_many :comment_voters&lt;/code&gt; to &lt;code&gt;Post&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;through: :comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;source: :voters&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :votes&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will correctly compute the data in just one query&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&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;comment_voters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And, you will also be able to preload the &lt;code&gt;comment_voters&lt;/code&gt; just as any other association.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comment_voters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;comment_voters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But&amp;hellip;&lt;/p&gt;

&lt;p&gt;If you see the executed queries, it could be not as performant as you need&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;irb(main):055:0&amp;gt; Post.preload(:comment_voters).to_a.count

Post Load (0.7ms)  SELECT "posts".* FROM "posts"

Comment Load (3.5ms)  SELECT "comments".* FROM "comments" WHERE
"comments"."post_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?)  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4],
["post_id", 5], ["post_id", 6], ["post_id", 7], ["post_id", 8], ["post_id", 9],
["post_id", 10], ["post_id", 11], ["post_id", 12], ["post_id", 13], ["post_id",
14], ["post_id", 15], ["post_id", 16]...

CommentVote Load (18.3ms)  SELECT "comment_votes".* FROM "comment_votes" WHERE
"comment_votes"."comment_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
95, 96, 97, 98, 99, 100...

User Load (0.2ms)  SELECT DISTINCT "users".* FROM "users" WHERE "users"."id" IN
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["id", 8], ["id", 5], ["id", 1], ["id", 6],
["id", 4], ["id", 7], ["id", 3], ["id", 10], ["id", 2], ["id", 9]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Maybe you can try to &lt;code&gt;eager_load&lt;/code&gt;, but maybe it could not be what you need either&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;irb(main):059:0&amp;gt; posts = Post.eager_load(:comment_voters).to_a

SQL (47.0ms)  SELECT "posts"."id" AS t0_r0, "posts"."author_id" AS t0_r1,
"posts"."title" AS t0_r2, "posts"."body" AS t0_r3, "posts"."created_at" AS
t0_r4, "posts"."updated_at" AS t0_r5, "users"."id" AS t1_r0, "users"."name" AS
t1_r1, "users"."created_at" AS t1_r2, "users"."updated_at" AS t1_r3 FROM
"posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" LEFT
OUTER JOIN "comment_votes" ON "comment_votes"."comment_id" = "comments"."id"
LEFT OUTER JOIN "users" ON "users"."id" = "comment_votes"."voter_id"
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="custom_query"&gt;Trying a custom query&lt;/h2&gt;

&lt;p&gt;Maybe what you want is to write a custom query, because you don&amp;rsquo;t want to fetch data that you don&amp;rsquo;t need.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s imagine that what you need is to fetch just the users with voted comments
on a collection of posts. Without preloading anything else, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# selects the user attributes and the post_id of the comment.&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;User Load (16.5ms)  SELECT DISTINCT users.*, comments.post_id FROM "users"
INNER JOIN "comment_votes" ON "comment_votes"."voter_id" = "users"."id" INNER
JOIN "comments" ON "comments"."id" = "comment_votes"."comment_id" WHERE
"comments"."post_id" IN (SELECT "posts"."id" FROM "posts")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then group this voters by &lt;code&gt;post_id&lt;/code&gt;, and then in the view you can pick the voters for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# selects the user attributes and the post_id of the comment.&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_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="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="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;[&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;id&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;:name&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;p&gt;That&amp;rsquo;s ok, but&amp;hellip;&lt;/p&gt;

&lt;p&gt;What if you don&amp;rsquo;t want to expose that code in your controller or view?&amp;hellip; What if instead we move
that to a model object?&amp;hellip; Where should you put that code?&lt;/p&gt;

&lt;h2 id="preload_object"&gt;A preload object&lt;/h2&gt;

&lt;p&gt;There are many options, but one of them, is to create a new object to help you with two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch the voters for the posts in one call to the database&lt;/li&gt;
&lt;li&gt;Return the voters for a given post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example you can call the object &lt;code&gt;Post::CommentVotersPreload&lt;/code&gt; and use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommentVotersPreload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&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;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;:name&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;p&gt;And the implementation could look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommentVotersPreload&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;comment_voters&lt;/span&gt;
    &lt;span class="vi"&gt;@comment_voters&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;fetch_records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_records&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&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;p&gt;You can use this kind of objects in many different problems.&lt;/p&gt;

&lt;p&gt;What you will need is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;fetch_records&lt;/code&gt; method to fetch the records you want to preload in one call to the database (or at least not n+1 queries).&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;find_for&lt;/code&gt; method to return the records related to each associated object.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="other_examples"&gt;Other examples&lt;/h2&gt;

&lt;p&gt;If you need more examples to understand the idea, you can check this other examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_all"&gt;An object to fetch all and select just the latest N&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_just_latest_n_window_functions"&gt;An object to fetch just the latest N for each record using window functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_just_latest_n_lateral_join"&gt;An object to fetch just the latest N for each record using lateral join&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="when"&gt;When should you use it?&lt;/h2&gt;

&lt;p&gt;The answer to this question is not that simple (at least for me).&lt;/p&gt;

&lt;p&gt;What I could recommend you is to first try to solve the problem in a &amp;ldquo;railsy&amp;rdquo;
way&amp;hellip; Try to use the mechanisms, provided by rails. Normally those mechanisms
will compose better with other parts of the framework.&lt;/p&gt;

&lt;p&gt;If you don&amp;rsquo;t know how to do it, because you don&amp;rsquo;t know enough about rails, try
to study the &lt;a href="https://guides.rubyonrails.org"&gt;rails guides&lt;/a&gt;, specially the
&lt;a href="https://guides.rubyonrails.org/association_basics.html"&gt;associations guide&lt;/a&gt;,
and the &lt;a href="https://guides.rubyonrails.org/active_record_querying.html"&gt;query interface
guide&lt;/a&gt;.  You can
also study my &lt;a href="guide-for-preloading-associations-in-rails.html"&gt;guide for preloading
associations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But if you already know what rails provides, and you can&amp;rsquo;t find a way to do
what you need to do&amp;hellip; or you need something more efficient&amp;hellip; or you just
think that the final code is better&amp;hellip;&lt;/p&gt;

&lt;p&gt;Then go for it and use a &amp;ldquo;preload object&amp;rdquo;, just try to have a reason.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A guide for preloading associations in rails</title>
    <link rel="alternate" href="https://bhserna.com/guide-for-preloading-associations-in-rails"/>
    <id>https://bhserna.com/guide-for-preloading-associations-in-rails</id>
    <published>2021-08-09T12:56:00Z</published>
    <updated>2021-08-09T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe you are already familiar with &lt;code&gt;includes&lt;/code&gt; or &lt;code&gt;preload&lt;/code&gt;, but you know
that a lot of the time you will need more than just &lt;code&gt;preload(:comments)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is common to have complex nested associations, scopes that you need or want
to reuse, places where preloading all the associated records could hurt the
performance of your app.&lt;/p&gt;

&lt;p&gt;How do you work with complex nested associations?&amp;hellip; How do you simplify the
preloading of those nested associations?&amp;hellip; How do you &amp;ldquo;preload an scope&amp;rdquo;?&amp;hellip;
How do you preload just the latest n of each record?&lt;/p&gt;

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

&lt;p&gt;ActiveRecord gives us some ways to do some of those things, but some of
them are not in the documentation or is not that easy to find them.&lt;/p&gt;

&lt;p&gt;Well, this is a guide to help you get better on this specific skill of preloading
associations, for when you need a more than just a &lt;code&gt;preload(:comments)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It starts with the basics, with just a regular &lt;code&gt;has_many&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; or
&lt;code&gt;includes&lt;/code&gt;, and build from this to then show you things like&amp;hellip; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to preload nested associations&lt;/li&gt;
&lt;li&gt;How to define associations to help you simplify the preloading&lt;/li&gt;
&lt;li&gt;How to preload just a part of the association like an scope&lt;/li&gt;
&lt;li&gt;How to preload just the &amp;ldquo;top 1 per group&amp;rdquo; or the &amp;ldquo;latest of each&amp;rdquo;&lt;/li&gt;
&lt;li&gt;How to preload just the &amp;ldquo;top n per group&amp;rdquo; or the &amp;ldquo;latest n of each&amp;rdquo;&lt;/li&gt;
&lt;li&gt;How to use custom objects to represent a preloading&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
  &lt;li&gt;&lt;a href="#preload_direct_associations"&gt;Preload direct associations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_nested_associations"&gt;Preload nested associations&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_an_scope"&gt;Preload an scope&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_the_top_1_of_an_association"&gt;Preload the top 1 of an association&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_the_top_n_of_an_association"&gt;Preload the top n of an association&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#simplify_preloading_with_has_many_through"&gt;Simplify preloading with has_many through&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="#preload_object"&gt;Preload object&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="preload_direct_associations"&gt;Preload direct associations&lt;/h2&gt;

&lt;p&gt;You can preload direct associations &lt;code&gt;preload&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt; or &lt;code&gt;eager_load&lt;/code&gt;. They are very similar but with some differences.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;preload&lt;/code&gt; - Makes a new query to preload the associations&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eager_load&lt;/code&gt; - It forces the eager loading performing a LEFT OUTER JOIN.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;includes&lt;/code&gt; - By default works like &lt;code&gt;preload&lt;/code&gt;, but in some cases it will behave like &lt;code&gt;eager_load&lt;/code&gt;, normally when you are also adding some conditions to the query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you want to know the difference in more detail you can see 
&lt;a href="includes-preload-eager-load-joins-in-rails.html"&gt;What is the difference between includes, preload, eager_load and joins in ActiveRecord?&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For example given the next model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will be able to fetch the posts, preloading the comments doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or...&lt;/span&gt;
&lt;span class="n"&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;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or...&lt;/span&gt;
&lt;span class="n"&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;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if you ask for the comments of each post it will not require a new call to the database.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will be able to fetch the comments, preloading the posts doing:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or...&lt;/span&gt;
&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or...&lt;/span&gt;
&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in the same way, if you ask for the post of each comment it will not require a new call to the database.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="preload_nested_associations"&gt;Preload nested associations&lt;/h2&gt;

&lt;p&gt;For nested associations you can pass a hash with the root association as the key and the associations that you want to preload as an array.&lt;/p&gt;

&lt;p&gt;The array can include more nested associations.&lt;/p&gt;

&lt;p&gt;For example, given the next model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can fetch the posts preloading comments and users:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comments: :user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fetch comments preloading posts with author:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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;post: :author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comments&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;:post&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;:author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fetch posts preloading comments with users and votes&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comments: &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="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;:votes&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;:to_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fetch posts preloading comments with users, votes and voters&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comments: &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="ss"&gt;votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:voter&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:votes&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;:voter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fetch posts preloading author, comments and users&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments: :user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&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;:author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="preload_an_scope"&gt;Preload an scope&lt;/h2&gt;

&lt;p&gt;Some times you already have and scope that you want to reuse in a place where it can produce n+1 queries.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;popular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"votes_count &amp;gt; 5"&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :votes_count&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you ask for the popular comments of one post there is no problem.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But if you ask for the popular comments for each post on a list, you may have some n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&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;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Here we are doing a new call to the database for each post&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the bad news is that you can&amp;rsquo;t really &amp;ldquo;just preload a scope&amp;rdquo;&amp;hellip;&lt;/p&gt;

&lt;p&gt;But the good news is that you can solve the problem by creating a new association for the scope and preload that association.&lt;/p&gt;

&lt;p&gt;In the example, you can add a new &lt;code&gt;has_many: :popular_comments&lt;/code&gt;, passing the scope on the association declaration, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;popular_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;popular&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;popular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"votes_count &amp;gt; 5"&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;counter_cache: :votes_count&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, in a similar way, you will be able to ask for the popular comments of one post without problems.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popular_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But now, if you need to ask for the popular comments for each post on a list, you can preload them to avoid the n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:popular_comments&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;5&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="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="c1"&gt;# Here we will use the preloaded comments. No n+1 queries!&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;popular_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="preload_the_top_1_of_an_association"&gt;Preload the top 1 of an association&lt;/h2&gt;

&lt;p&gt;Imagine, that you have a model like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&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&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&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;p&gt;When you are fetching a single post there is no problem…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when you try to fetch a list of posts, the method &lt;a href="/why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record.html"&gt;seems to ignore the
includes&lt;/a&gt;
and runs a query for each post to get the &lt;code&gt;latest_comment&lt;/code&gt; for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One way of solving this problem is to instead order comments on the association
definition and to later just ask for the &lt;code&gt;last&lt;/code&gt; sorted comment.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;p&gt;And now you will be able to preload the &lt;code&gt;sorted_comments&lt;/code&gt; ask for the
&lt;code&gt;lastest_comment&lt;/code&gt; of each post without n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The problem with this method is that you will fetch all the comments for every
post in the list and &lt;a href="/benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;sometimes it could be a problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But, you can avoid this problem!&lt;/p&gt;

&lt;p&gt;You can use &lt;code&gt;has_one&lt;/code&gt; association for the &lt;code&gt;latest_comment&lt;/code&gt;, by providing a scope
with just the &lt;code&gt;latest_comment&lt;/code&gt; for each &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;latest_comment_per_posts&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment_per_posts&lt;/span&gt;
    &lt;span class="n"&gt;latest_comments_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;latest_comments_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here, in the first line of &lt;code&gt;latest_comment_per_post&lt;/code&gt; we are building the sql to find the latest comment id for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;latest_comments_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Something interesting is that the query &lt;a href="why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record.html"&gt;will not be executed&lt;/a&gt;
it will just pass the ActiveRecord::Relation to the where clause.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;latest_comments_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you can preload the has one association and fetch just the &lt;code&gt;latest_comment&lt;/code&gt; for each post. &lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;You can explore other ways of doing it on &lt;a href="5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;5 ways to fix the latest-comment n+1 problem&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id="preload_the_top_n_of_an_association"&gt;Preload the top n of an association&lt;/h2&gt;

&lt;p&gt;Sometimes you need to fetch just the top elements on a list, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 3 most popular comments&lt;/li&gt;
&lt;li&gt;The latest 3 comments&lt;/li&gt;
&lt;li&gt;The latest review of a product&lt;/li&gt;
&lt;li&gt;The 3 better prices for a product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, for the next model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we want to fetch the latest 3 comments for a post, we can write something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comments&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;id: :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;3&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But as we saw on &lt;a href="#preload_an_scope"&gt;preload an scope&lt;/a&gt;, if we try to use it on a list we will get n+1 queries&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="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="c1"&gt;# This will create a call for each post&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;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;And in this case, you can&amp;rsquo;t just move the scope to the association, because it will fetch only 3 comments in total, not for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
&lt;span class="k"&gt;end&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;:latest_comments&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;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&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;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;On the the sql produced on the preload, you can see that we are just fetching 3 comments in total.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&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="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"comments"&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;"comments"&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"post_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"comments"&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;DESC&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One way to solve the problem could be to, keep just the &lt;code&gt;order&lt;/code&gt; on the
association declaration, and do the selection in ruby, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&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;:sorted_comments&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;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&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;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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="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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;But if you have many comments per post it can be a problem. Because it will load them all.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can check the &lt;a href="benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;Benchmarks for the fixes to the latest-comment n+1 problem&lt;/a&gt; to get more specific on what is &amp;ldquo;many comments&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One way to solve this problem is to use a lateral join. There are more, but you can start with this.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;latest_comments_per_post&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_per_post&lt;/span&gt;
    &lt;span class="n"&gt;latest_comments&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest_comments.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM comments
          WHERE post_id = posts.id
          ORDER BY id DESC LIMIT 3
        ) AS latest_comments ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;id: :desc&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;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;:latest_comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;On the method &lt;code&gt;latest_comments_per_posts&lt;/code&gt; we are fetching just the latest three comments per posts.
Because it will apply the limit &amp;ldquo;inside&amp;rdquo; the join.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_per_post&lt;/span&gt;
  &lt;span class="n"&gt;latest_comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;
     &lt;span class="c1"&gt;# Here we select just the "comments"&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="s2"&gt;"latest_comments.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Here we are doing the limit inside the join,&lt;/span&gt;
    &lt;span class="c1"&gt;# it will apply it for the comments of each post.&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      JOIN LATERAL (
        SELECT * FROM comments
        WHERE post_id = posts.id
        ORDER BY id DESC LIMIT 3
      ) AS latest_comments ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

  &lt;span class="c1"&gt;# Here we use the returned "latest_comments" and&lt;/span&gt;
  &lt;span class="c1"&gt;# we name them like "comments". It will tell rails&lt;/span&gt;
  &lt;span class="c1"&gt;# to instantiate Comment objects&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;id: :desc&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;p&gt;&lt;em&gt;You can explore other ways of doing it on &lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html"&gt;5 ways to fetch the latest-N-of-each record on Rails&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id="simplify_preloading_with_has_many_through"&gt;Simplify preloading with has_many through&lt;/h2&gt;

&lt;p&gt;Preloading nested associations is not that hard, but sometimes could be unnecessary for certain actions.&lt;/p&gt;

&lt;p&gt;For example, with the next model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Imagine that you want to fetch the people that have a vote on a comment in a post, the &amp;ldquo;comment voters&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;To implement it, you can have a method in the post to do that&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;comment_voters&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;votes: :voter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:votes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:voter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&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;p&gt;And it will correctly preload the votes and voters to compute the result&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;puts&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;comment_voters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But if you try to do it for a list of post, it will execute n+1 queries, and also you will be loading a bunch of data that you don&amp;rsquo;t need.&lt;/p&gt;

&lt;p&gt;You can try to do a &amp;ldquo;better query&amp;rdquo; to avoid preloading all that data, but making it work in a list will be very hard.&lt;/p&gt;

&lt;p&gt;Fortunately you can use a &lt;code&gt;has_many through:&lt;/code&gt; associations to do this operation in a much simpler way.&lt;/p&gt;

&lt;p&gt;You can add a &lt;code&gt;has_many :voters&lt;/code&gt; to &lt;code&gt;Comment&lt;/code&gt; and a &lt;code&gt;has_many :comment_voters&lt;/code&gt; to &lt;code&gt;Post.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;through: :comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;source: :voters&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :votes&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will correctly compute the data in just one query&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;puts&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;comment_voters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But, you will also be able to preload the &lt;code&gt;comment_voters&lt;/code&gt; just as any other association.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comment_voters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;comment_voters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="preload_object"&gt;Preload object&lt;/h2&gt;

&lt;p&gt;Sometimes using using the default preloading mechanisms from rails, &lt;code&gt;preload&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt; and &lt;code&gt;eager_load&lt;/code&gt; is not possible&amp;hellip; or maybe it is, but you just can&amp;rsquo;t figure it out how to do it.&lt;/p&gt;

&lt;p&gt;Maybe you know how to represent the association with a &lt;code&gt;has_many :through&lt;/code&gt;, but you need something faster, or to use less memory.&lt;/p&gt;

&lt;p&gt;In that kind of situations, one thing that you can do is to write a custom object to represent that preload.&lt;/p&gt;

&lt;p&gt;For example, with the example from the last section:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;through: :comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;source: :voters&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;Comment&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"CommentVote"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;through: :votes&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;CommentVote&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&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;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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You know that this will correctly compute the data in just one query&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;first&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;comment_voters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And, that you will also be able to preload the &lt;code&gt;comment_voters&lt;/code&gt; just as any other association.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comment_voters&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;comment_voters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But&amp;hellip;&lt;/p&gt;

&lt;p&gt;If you see the executed queries, it could be not as performant as you need&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;irb(main):055:0&amp;gt; Post.preload(:comment_voters).to_a.count

Post Load (0.7ms)  SELECT "posts".* FROM "posts"

Comment Load (3.5ms)  SELECT "comments".* FROM "comments" WHERE
"comments"."post_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?)  [["post_id", 1], ["post_id", 2], ["post_id", 3], ["post_id", 4],
["post_id", 5], ["post_id", 6], ["post_id", 7], ["post_id", 8], ["post_id", 9],
["post_id", 10], ["post_id", 11], ["post_id", 12], ["post_id", 13], ["post_id",
14], ["post_id", 15], ["post_id", 16]...

CommentVote Load (18.3ms)  SELECT "comment_votes".* FROM "comment_votes" WHERE
"comment_votes"."comment_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
95, 96, 97, 98, 99, 100...

User Load (0.2ms)  SELECT DISTINCT "users".* FROM "users" WHERE "users"."id" IN
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["id", 8], ["id", 5], ["id", 1], ["id", 6],
["id", 4], ["id", 7], ["id", 3], ["id", 10], ["id", 2], ["id", 9]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Maybe you can try to &lt;code&gt;eager_load&lt;/code&gt;, but maybe it could not be what you need either&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;irb(main):059:0&amp;gt; posts = Post.eager_load(:comment_voters).to_a

SQL (47.0ms)  SELECT "posts"."id" AS t0_r0, "posts"."author_id" AS t0_r1,
"posts"."title" AS t0_r2, "posts"."body" AS t0_r3, "posts"."created_at" AS
t0_r4, "posts"."updated_at" AS t0_r5, "users"."id" AS t1_r0, "users"."name" AS
t1_r1, "users"."created_at" AS t1_r2, "users"."updated_at" AS t1_r3 FROM
"posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" LEFT
OUTER JOIN "comment_votes" ON "comment_votes"."comment_id" = "comments"."id"
LEFT OUTER JOIN "users" ON "users"."id" = "comment_votes"."voter_id"
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Trying a custom query&lt;/h3&gt;

&lt;p&gt;Maybe what you want is to write a custom query, because you don&amp;rsquo;t want to fetch data that you don&amp;rsquo;t need.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s imagine that what you need is to fetch just the users with voted comments on a collection of posts. Without preloading anything else, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# selects the user attributes and the post_id of the comment.&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;User Load (16.5ms)  SELECT DISTINCT users.*, comments.post_id FROM "users"
INNER JOIN "comment_votes" ON "comment_votes"."voter_id" = "users"."id" INNER
JOIN "comments" ON "comments"."id" = "comment_votes"."comment_id" WHERE
"comments"."post_id" IN (SELECT "posts"."id" FROM "posts")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can then group this voters by &lt;code&gt;post_id&lt;/code&gt;, and then in the view you can pick the voters for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# selects the user attributes and the post_id of the comment.&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_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="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="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;[&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;id&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;:name&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;p&gt;That&amp;rsquo;s ok, but&amp;hellip;&lt;/p&gt;

&lt;p&gt;What if you don&amp;rsquo;t want to expose that code in your controller or view?&amp;hellip; What if instead we move
that to a model object?&amp;hellip; Where should you put that code?&lt;/p&gt;

&lt;h3&gt;A preload object&lt;/h3&gt;

&lt;p&gt;There are many options, but one of them, is to create a new object to help you with two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch the voters for the posts in one call to the database&lt;/li&gt;
&lt;li&gt;Return the voters for a given post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example you can call the object &lt;code&gt;Post::CommentVotersPreload&lt;/code&gt; and use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comment_voters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommentVotersPreload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&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;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;:name&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;p&gt;And the implementation could look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommentVotersPreload&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;comment_voters&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;comment_voters&lt;/span&gt;
    &lt;span class="vi"&gt;@comment_voters&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;fetch_records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_records&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"users.*, comments.post_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;comment_votes: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:comment&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;post_id: &lt;/span&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&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;p&gt;You can use this kind of objects in many different problems.&lt;/p&gt;

&lt;p&gt;What you will need is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;fetch_records&lt;/code&gt; method to fetch the records you want to preload in one call to the database (or at least not n+1 queries).&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;find_for&lt;/code&gt; method to return the records related to each associated object.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need more examples to understand the idea, you can check this other examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_all"&gt;An object to fetch all and select just the latest N&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_just_latest_n_window_functions"&gt;An object to fetch just the latest N for each record using window functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html#fetch_just_latest_n_lateral_join"&gt;An object to fetch just the latest N for each record using lateral join&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;When should you use it?&lt;/h3&gt;

&lt;p&gt;The answer to this question is not that simple (at least for me).&lt;/p&gt;

&lt;p&gt;What I could recommend you is to first try to solve the problem in a &amp;ldquo;railsy&amp;rdquo;
way, using the mechanisms, provided by rails. Normally those mechanisms
will compose better with other parts of the framework.&lt;/p&gt;

&lt;p&gt;But if you have already tried what rails provides, and you can&amp;rsquo;t find a way to do
what you need to do&amp;hellip; or you need something more efficient&amp;hellip; or you just
think that the final code is better&amp;hellip;&lt;/p&gt;

&lt;p&gt;Then go for it and use a &amp;ldquo;preload object&amp;rdquo;, just try to have a reason.&lt;/p&gt;

&lt;h2&gt;Final notes&lt;/h2&gt;

&lt;p&gt;You made to the end! Congrats!&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s recap what you can do now&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preload direct associations&lt;/li&gt;
&lt;li&gt;Preload nested associations&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Preload an scope&amp;rdquo;&amp;hellip; you know that you need an association&lt;/li&gt;
&lt;li&gt;Preload the top n of an association&amp;hellip; with a lateral join an maybe with other methods&lt;/li&gt;
&lt;li&gt;Simplify a preloading with a has_many through association&lt;/li&gt;
&lt;li&gt;Use a preload object to represent a preload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope that you can feel that now you are a better rails developer!&lt;/p&gt;

&lt;p&gt;I hope you have enjoyed this guide!&lt;/p&gt;

&lt;p&gt;If you have some feedback on how this guide can help you better, please write a comment =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What is an n+1 queries problem?</title>
    <link rel="alternate" href="https://bhserna.com/what-is-an-n-plus-1-queries-problem"/>
    <id>https://bhserna.com/what-is-an-n-plus-1-queries-problem</id>
    <published>2021-07-27T12:56:00Z</published>
    <updated>2021-07-27T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;An n+1 queries problem means that a query is executed for every result of a previous query. &lt;/p&gt;

&lt;p&gt;For example, with this records&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you execute the next code&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;You will execute&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query to fetch the posts (&lt;code&gt;Post.all&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;And &lt;code&gt;n&lt;/code&gt; queries to fetch the &lt;code&gt;comments&lt;/code&gt; of each &lt;code&gt;post&lt;/code&gt; (&lt;code&gt;post.comments&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 posts, it will execute 3 queries (2 + 1)&lt;/li&gt;
&lt;li&gt;5 posts, it will execute 6 queries (5 + 1)&lt;/li&gt;
&lt;li&gt;1000 posts, it will execute 1001 queries (1000 + 1)&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Tool to run the examples for the 5 ways to fetch the latest-n-of-each record</title>
    <link rel="alternate" href="https://bhserna.com/tool-to-run-the-latest-n-of-each-record-examples"/>
    <id>https://bhserna.com/tool-to-run-the-latest-n-of-each-record-examples</id>
    <published>2021-07-22T12:56:00Z</published>
    <updated>2021-07-22T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;A common cause of n+1 queries is fetching the “latest-N-of-each” record on a list of records.&lt;/p&gt;

&lt;p&gt;Some examples of this problem are trying to get…&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;The latest comments for each post&lt;/li&gt;
&lt;li&gt;The latest payments for each customer&lt;/li&gt;
&lt;li&gt;The latest review for each product&lt;/li&gt;
&lt;li&gt;The better prices for each product&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have one post named &lt;a href="5-ways-to-fetch-the-latest-n-of-each-record-on-rails.html"&gt;5 ways to fetch the latest-N-of-each record on Rails&lt;/a&gt;
where I share different ways to solve this problem. Here I want to share with you the code that I used in the post.&lt;/p&gt;

&lt;p&gt;You can use it to &lt;strong&gt;run the examples&lt;/strong&gt;, and play with the code and seed values, to pick the right solution for you current case.&lt;/p&gt;

&lt;h2&gt;How to run the examples&lt;/h2&gt;

&lt;p&gt;1 - &lt;strong&gt;Install the dependencies&lt;/strong&gt; with &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;2 - &lt;strong&gt;Database setup&lt;/strong&gt; - run the command:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby db/setup.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3 - &lt;strong&gt;Run the examples&lt;/strong&gt; with &lt;code&gt;ruby fixes_examples/&amp;lt;file name&amp;gt;&lt;/code&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;ruby fixes_examples/01_fetch_all_and_select_for_each.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;4 - &lt;strong&gt;Change the posts and comments count&lt;/strong&gt;  on &lt;code&gt;db/seeds.rb&lt;/code&gt; you can change
   &lt;code&gt;Seeds.run(posts_count: 10, comments_count: 10)&lt;/code&gt; and re-run &lt;code&gt;ruby
   db/setup.rb&lt;/code&gt; to test different scenarios.&lt;/p&gt;

&lt;h2&gt;Get the code to run the examples&lt;/h2&gt;

&lt;p&gt;Subscribe, and get the code to run each of the examples in the post. You will receive a zip with the code.&lt;/p&gt;

&lt;div class="mt3 pa3 ba b--light-blue bw2 bg-washed-blue"&gt;
  &lt;script async data-uid="03f2a77f90" src="https://bhserna.ck.page/03f2a77f90/index.js"&gt;&lt;/script&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>How to test that an email was sent on a unit/micro test</title>
    <link rel="alternate" href="https://bhserna.com/how-to-test-that-an-specific-mail-was-sent-in-rails"/>
    <id>https://bhserna.com/how-to-test-that-an-specific-mail-was-sent-in-rails</id>
    <published>2021-07-12T00:28:00Z</published>
    <updated>2021-07-12T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;There are different practices, that I know, to test that an email was sent in Rails&amp;hellip;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Check for the &lt;code&gt;ActionMailer::Base.deliveries&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Spy your own sender objects&lt;/li&gt;
&lt;li&gt;Use doubles for your mailer objects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of them have pros and cons and will help you test different things.&lt;/p&gt;

&lt;p&gt;That is why I think that is good to understand all of them because it will help you decide which method is better case by case.&lt;/p&gt;

&lt;h2&gt;1. Check for the ActionMailer::Base.deliveries&lt;/h2&gt;

&lt;p&gt;It will be similar to what you will do on a &lt;a href="test-that-an-email-was-sent-feature-spec.html"&gt;feature/capybara spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will check the &lt;code&gt;ActionMailer::Base.deliveries&lt;/code&gt; to see if the email that you want is there.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#create_invitation"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"sends the invitation email"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;

    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_invitation&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;"Mary"&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;"mary@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_mail_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mary@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"You have been invited to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_mail_to&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="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliveries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&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;email&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;2. Spy your own sender objects&lt;/h2&gt;

&lt;p&gt;Using rspec, you can expect that an object will receive the right method that will actually deliver the mail.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#send_invitation_emails"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"sends the email for all invitations"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;
    &lt;span class="n"&gt;invitation1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;
    &lt;span class="n"&gt;invitation2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;event: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invitation1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invitation2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:send_email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_invitation_emails&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;h2&gt;3. Use doubles for your mailer objects&lt;/h2&gt;

&lt;p&gt;Using rspec, you can allow your mailer to receive &lt;code&gt;new&lt;/code&gt; with the right
attributes and then return an object that will receive the method that actually
delivers the mail.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Invitation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"#send_email"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"sends the new_invitation email"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;invitation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;invitation&lt;/span&gt;

    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Mail"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EventMailer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new_invitation&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:deliver_later&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;invitation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_email&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;h2&gt;What method should you use?&lt;/h2&gt;

&lt;p&gt;As you can see not all tests will test exactly the same thing. And most of
the time you won&amp;rsquo;t need all of them.&lt;/p&gt;

&lt;p&gt;If you are struggling deciding which test to write, I recommend you to start
with the &lt;a href="test-that-an-email-was-sent-feature-spec.html"&gt;feature/capybara
spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then if you fill that you need to test something at the unit/micro level, you
can try this methods in order, but just moving to the next method only if you really
feel that is necesary.&lt;/p&gt;

&lt;p&gt;So, you can start with the first method, then if you need more confidence you
can try the second, and finally the third method if you really think that is
necessary.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to test that an email was sent on a system spec</title>
    <link rel="alternate" href="https://bhserna.com/test-that-an-email-was-sent-feature-spec"/>
    <id>https://bhserna.com/test-that-an-email-was-sent-feature-spec</id>
    <published>2021-07-02T00:28:00Z</published>
    <updated>2021-07-02T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;If you want to test that an email was sent on a system spec in rails&amp;hellip;&lt;/p&gt;

&lt;p&gt;What you can do is to check the &lt;code&gt;ActionMailer::Base.deliveries&lt;/code&gt; to see if the email that you want is there.&lt;/p&gt;

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

&lt;p&gt;You can look for the mail with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliveries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incude?&lt;/span&gt; &lt;span class="s2"&gt;"mary@example.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Maybe you can write a helper method to make the code a little more readable:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_mail_to&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="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliveries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&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;email&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;p&gt;And you can check if the subject is right with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"Super subject!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;A full example&lt;/h2&gt;

&lt;p&gt;Imagine that you are building an app to manage events, and you are writing a feature to invite guests to an event via email.&lt;/p&gt;

&lt;p&gt;So, in your system spec you want to check that when you create an invitation the system will send the right email to the invited guest.&lt;/p&gt;

&lt;p&gt;Then you can write something like this in your spec:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Event owner invites guest"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"successfully"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;owner&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;event_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;create_invitation&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;"Mary"&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;"mary@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_mail_to&lt;/span&gt; &lt;span class="s2"&gt;"mary@example.com"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"You have been invited to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_invitation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="s2"&gt;"Invite guest"&lt;/span&gt;
    &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s2"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Create invitation"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_mail_to&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="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliveries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&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;email&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Guide to name scopes based on column types</title>
    <link rel="alternate" href="https://bhserna.com/guide-to-name-scopes-based-on-column-types"/>
    <id>https://bhserna.com/guide-to-name-scopes-based-on-column-types</id>
    <published>2021-06-29T00:28:00Z</published>
    <updated>2021-06-29T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;As you may now there are &lt;a href="https://martinfowler.com/bliki/TwoHardThings.html"&gt;two hard things&lt;/a&gt; in computer science and one of them is naming things. &lt;/p&gt;

&lt;p&gt;And although naming things is an art, is not bad to have a little guide on how to do it.&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s why I want to share with you a guide on how to name scopes that depend on a single column based on its type.&lt;/p&gt;

&lt;p&gt;This suggestion is based on a very interesting gem called &lt;a href="https://github.com/BaseSecrete/type_scopes"&gt;type_scopes&lt;/a&gt;, that I found on &lt;a href="https://www.reddit.com/r/rails/comments/o5m3vp/type_scopes_automatic_scopes_for_activerecord/h2rm4ei/?context=3"&gt;reddit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think that even if you don&amp;rsquo;t use the gem you can benefit from the names that it is proposing. Maybe it won&amp;rsquo;t fit all cases but I think that it could make our work a little easier.&lt;/p&gt;

&lt;h2 id="date"&gt;Date&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;lt;= '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;gt;= '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;gt; '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;lt; '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on BETWEEN '2017-09-06' AND '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_not_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on NOT BETWEEN '2017-09-06' AND '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;gt; '2017-09-06' AND received_on &amp;lt; '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_not_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("received_on &amp;lt;= '2017-09-06' OR received_on &amp;gt;= '2017-09-07'")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="datetime"&gt;Datetime&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;lt;= '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;gt;= '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;gt; '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;lt; '2017-09-06'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at BETWEEN '2017-09-06' AND '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_not_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at NOT BETWEEN '2017-09-06' AND '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;gt; '2017-09-06' AND completed_at &amp;lt; '2017-09-07'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed_not_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2017-09-06"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2017-09-07"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed_at &amp;lt;= '2017-09-06' OR completed_at &amp;gt;= '2017-09-07'")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="numeric"&gt;Numeric&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;lt;= 100")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;gt;= 100")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;gt; 100")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_below&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;lt; 100")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price BETWEEN 100 AND 200")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_not_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price NOT BETWEEN 100 AND 200")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;gt; 100 AND price &amp;lt; 200")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price_not_within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("price &amp;lt;= 100 OR price &amp;gt;= 200")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="string"&gt;String&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name LIKE '%foo%'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sensitive: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name ILIKE '%foo%'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name LIKE 'foo%'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_starts_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sensitive: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name ILIKE 'foo%'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_ends_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name LIKE '%foo'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_ends_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sensitive: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name ILIKE '%foo'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%foo%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name LIKE '%foo%'")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name_ilike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%foo%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("name ILIKE '%foo%'")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="boolean"&gt;Boolean&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completed&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed = true")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not_completed&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("completed = false")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_native&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("is_native = true")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_not_native&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("is_native = false")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_invitation&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("has_invitation = true")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_not_invitation&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("has_invitation = false")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;was_terminated&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("was_terminated = true")&lt;/span&gt;

&lt;span class="no"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;was_not_terminated&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; where("was_terminated = false")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>List of alternatives to heroku</title>
    <link rel="alternate" href="https://bhserna.com/list-of-alternatives-to-heroku"/>
    <id>https://bhserna.com/list-of-alternatives-to-heroku</id>
    <published>2021-06-22T00:28:00Z</published>
    <updated>2021-06-22T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I like heroku, it is very easy to use, but sometimes you just can&amp;rsquo;t use it&amp;hellip; &lt;/p&gt;

&lt;p&gt;Maybe it is too expensive for your project.
Maybe you need something that it does not provide, like an integrated &amp;ldquo;S3-storage&amp;rdquo;.
Maybe for some reason you are forced to run on AWS.
Or maybe you prefer just a layer on top of the hosting provider.&lt;/p&gt;

&lt;p&gt;Here you can find a list of tools/services with similar characteristics that
you can check if you need find an alternative.&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://aws.amazon.com/elasticbeanstalk/"&gt;AWS elastic beanstalk&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;AWS Elastic Beanstalk is an easy-to-use service for deploying and scaling web applications and services developed with Java, .NET, PHP, Node.js, Python, Ruby, Go, and Docker on familiar servers such as Apache, Nginx, Passenger, and IIS.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
There is no additional charge for AWS Elastic Beanstalk. You pay for AWS resources. You can see more on &lt;a href="https://aws.amazon.com/elasticbeanstalk/pricing/"&gt;their pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://www.cloud66.com/rails"&gt;Cloud 66&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;Ops Tools for Devs. Our products are like your in-house DevOps team. They take care of your uptime, scaling, and security. Just like your own team would, but with the experience of millions of deployments per day.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
Price: 14.99 / server per month. You can see more on &lt;a href="https://www.cloud66.com/rails/pricing"&gt;their pricing page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://www.digitalocean.com/products/app-platform/"&gt;Digital Ocean App Platform&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;Build, deploy, and scale apps quickly using a simple, fully managed solution.
We’ll handle the infrastructure, app runtimes and dependencies, so that you can
push code to production in just a few clicks.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
Starts at $5/mo&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://dokku.com/"&gt;Dokku&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;The smallest PaaS implementation you&amp;rsquo;ve ever seen. Dokku helps you build and manage the lifecycle of applications&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
Free&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;Deploy App Servers Close to Your Users. Run your full stack apps (and databases!) all over the world. No ops required.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
Starts free. You can see more on &lt;a href="https://fly.io/docs/about/pricing/"&gt;their pricing page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://www.hatchbox.io/"&gt;Hatchbox.io&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;Build, deploy, and manage Rails apps. Ruby on Rails apps on your own hosting provider in minutes and without hassle.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
Starts at $29 per month, deploy as many apps as you want.&lt;/p&gt;

&lt;h2&gt;&lt;a href="https://render.com/"&gt;Render&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Description:&lt;/strong&gt;
&lt;blockquote class="bl b--light-gray bw2 pa3 ma0"&gt;
&amp;ldquo;Render is a unified cloud to build and run all your apps and websites with free SSL, a global CDN, private networks and auto deploys from Git.&amp;rdquo;
&lt;/blockquote&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt;
From $7/mo&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Guide to justify the heroku's price to your boss or clients</title>
    <link rel="alternate" href="https://bhserna.com/guide-to-justify-heroku-price"/>
    <id>https://bhserna.com/guide-to-justify-heroku-price</id>
    <published>2021-06-14T00:28:00Z</published>
    <updated>2021-06-14T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Maybe your are thinking that you should just use Heroku and move on with your life. Do something that provides value to the customer instead of host configurations.&lt;/p&gt;

&lt;p&gt;But is not always that easy, because if you just compare the cost of GB of Ram or CPUs, it does not look that well&amp;hellip; $50 for 1gb is barely enough and maybe it won&amp;rsquo;t let you sleep at night&amp;hellip; But then $250 for 2.5gb or $500 for 14gb could  be more than enough.&lt;/p&gt;

&lt;p&gt;So you would need to ask your boss or clients something between $50-500/month, and maybe it could look like too much.&lt;/p&gt;

&lt;p&gt;Maybe &lt;a href="/list-to-check-if-heroku-is-expensive-for-your-project.html"&gt;Heroku is actually really expensive for your project&lt;/a&gt;, but maybe not, here I want to share with you some things that you could do to justify the price to your boss or client if you think that heroku (or another PaSS) is the right choice for your project.&lt;/p&gt;

&lt;h2&gt;Identify the things that you need from heroku&lt;/h2&gt;

&lt;p&gt;Remember the things that heroku is doing for you, that your project needs, and use them as a tool to justify the price to your boss or client.&lt;/p&gt;

&lt;p&gt;Here a list some of the features of heroku but there are more&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backup system&lt;/li&gt;
&lt;li&gt;Git push based, deployment system&lt;/li&gt;
&lt;li&gt;Automatic OS updates&lt;/li&gt;
&lt;li&gt;Review apps&lt;/li&gt;
&lt;li&gt;Semi-automatic scaling&lt;/li&gt;
&lt;li&gt;Addonds&lt;/li&gt;
&lt;li&gt;Environment variables system&lt;/li&gt;
&lt;li&gt;High compliance requirements&lt;/li&gt;
&lt;li&gt;Process monitoring system&lt;/li&gt;
&lt;li&gt;Access management&lt;/li&gt;
&lt;li&gt;Continuos integration system&lt;/li&gt;
&lt;li&gt;Github integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Remember what they are actually paying for developers&lt;/h2&gt;

&lt;p&gt;If you think that they are already paying between $20 and $200 per hour, maybe increasing your price a little to cover other $50-$500 per month, could be possible if you list all the things that they could have for that money (take them from the list above).&lt;/p&gt;

&lt;h2&gt;Remember that devops is a thing&lt;/h2&gt;

&lt;p&gt;If your service includes keeping the application up, charging for that is ok.&lt;/p&gt;

&lt;p&gt;Think how much this could cost if you outsource it&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many hours from you?&lt;/li&gt;
&lt;li&gt;How many hours from the devops person/team?&lt;/li&gt;
&lt;li&gt;How much in plain hosting?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could use that number to justify the increase of your price.&lt;/p&gt;

&lt;h2&gt;Remember that devops can go wrong&lt;/h2&gt;

&lt;p&gt;If you are not a devops pro, or even if you hire a team of senior devops, things can go wrong and its possible that even after spending a lot of money on the migration from heroku, the project must be moved to heroku again.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>List of questions to check if heroku is really expensive for your project</title>
    <link rel="alternate" href="https://bhserna.com/list-to-check-if-heroku-is-expensive-for-your-project"/>
    <id>https://bhserna.com/list-to-check-if-heroku-is-expensive-for-your-project</id>
    <published>2021-06-08T00:28:00Z</published>
    <updated>2021-06-08T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;For most of us, heroku is a really useful tool because it does so much for us, but in some cases it could look like it is really expensive&amp;hellip;&lt;/p&gt;

&lt;p&gt;&amp;hellip; Is it really expensive? Does it worth the time it saves on tooling, maintenance, devops, etc? Would you be able to replace it with AWS or other provider?&lt;/p&gt;

&lt;p&gt;I really don&amp;rsquo;t know. I think it really depends on you, your team and your current project.&lt;/p&gt;

&lt;p&gt;But if you are reading this article is because maybe you have a project where you are wondering if it really worth the price.&lt;/p&gt;

&lt;p&gt;So, here I want to share with you a list of questions, to help you decide if heroku is reall expensive for your project or not.&lt;/p&gt;

&lt;h2&gt;What do you need?&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do you even care about all the features in heroku or you just want something to play?&lt;/li&gt;
&lt;li&gt;How important is a backup system?&lt;/li&gt;
&lt;li&gt;How important is to just &lt;code&gt;git push&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;How important are automatic OS updates?&lt;/li&gt;
&lt;li&gt;How important are automatic security updates?&lt;/li&gt;
&lt;li&gt;How important are review apps?&lt;/li&gt;
&lt;li&gt;How important are the semi-automatic scaling?&lt;/li&gt;
&lt;li&gt;How many addons do you depend on that will be hard to setup with other cloud provider?&lt;/li&gt;
&lt;li&gt;Do you need high complience level requirements that heroku already can give you?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;What do you know?&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do you know how to run your app in another cloud provider?&lt;/li&gt;
&lt;li&gt;Do you know how to do a deployment setup for another cloud provider?&lt;/li&gt;
&lt;li&gt;Do you know how to setup a backup system in another cloud provider?&lt;/li&gt;
&lt;li&gt;Do you know how to do and keep track of OS updates?&lt;/li&gt;
&lt;li&gt;Do you know how to do and keep track of security updates?&lt;/li&gt;
&lt;li&gt;Do you know how setup a review apps system?&lt;/li&gt;
&lt;li&gt;Do you know how setup a resource scaling system?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Current bill&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Is your current bill larger than the bill of the other provider plus the salary cost of at least one senior devops?&lt;/li&gt;
&lt;li&gt;Are you aware that even hire two senior devops, for the migration from heroku can end in a fail as a project?&lt;/li&gt;
&lt;li&gt;Do you want to do all the required work to avoid the bill?&lt;/li&gt;
&lt;li&gt;Have you tried to pitch to you client, boss or the person who pays the bills how much does a migration will cost? (the other provider bill, plus the salary cost of at least one senior devops, even if this person if you)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;A rule of thumb&lt;/h2&gt;

&lt;p&gt;Normally Heroku will be more expensive than doing the work yourself on other provider, but it will almost always going to be cheaper than hiring a devops person to do on other provider what Heroku does for you.&lt;/p&gt;

&lt;p&gt;You can think of heroku as &amp;ldquo;hosting + devops&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;So, if you know some devops and are comfortable handling an emergency without a devops person, AWS or other providers will probably be cheaper than Heroku.&lt;/p&gt;

&lt;p&gt;If you would need to hire someone to handle devops or it will take much from your own time and you don&amp;rsquo;t want it, I think that you are better using Heroku even if it looks &amp;ldquo;expensive&amp;rdquo;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Extracting domain knowledge from a query method</title>
    <link rel="alternate" href="https://bhserna.com/extracting-domain-knowledge-from-a-query-method"/>
    <id>https://bhserna.com/extracting-domain-knowledge-from-a-query-method</id>
    <published>2021-05-27T00:28:00Z</published>
    <updated>2021-05-27T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you need to query for the payments received the &amp;ldquo;week before&amp;rdquo; of a given date.&lt;/p&gt;

&lt;p&gt;How would you expose this feature in your model?&lt;/p&gt;

&lt;p&gt;In this article I want to share some ways of doing it, by telling the story of what we did in a similar case&amp;hellip;&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Note: I am changing the names a little but the problem was mostly the same&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;Why do we need that query?&lt;/h2&gt;

&lt;p&gt;We needed that query because we wanted to send a notification to all users that
received a payment in the last week. That notification will be sent each week.&lt;/p&gt;

&lt;h2&gt;Our first approach&amp;hellip;&lt;/h2&gt;

&lt;p&gt;The first thing that we tried was to expose a class method/scope in the &lt;code&gt;Payment&lt;/code&gt; record, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_this_week&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We wrote some tests&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;".received_a_week_before"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received on last second"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_a_week_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&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;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received one second late"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_a_week_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&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;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received in the first second"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_a_week_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&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;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received on previous week"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_a_week_before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&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;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To end with an implementation like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment&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="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_this_week&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;Note: if you want to know about how to build the range, you can see &lt;a href="query-date-ranges-rails-active-record.html"&gt;Building a DateTime range to query for the records created a &amp;ldquo;week before&amp;rdquo;&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;This was ok, but&amp;hellip;&lt;/h2&gt;

&lt;p&gt;This was ok and it was working, but the name was not really that expressive, at least for us (on that moment&amp;hellip;).&lt;/p&gt;

&lt;p&gt;As I said, we were implementing this method because we wanted to send a notification that was going to be sent each week.&lt;/p&gt;

&lt;p&gt;Maybe we could have changed the name to something like
&lt;code&gt;Payment.for_weekly_notification&lt;/code&gt; but we started to think that this behavior,
should not be on the &lt;code&gt;Payment&lt;/code&gt; class, instead it could belong to a more specific
object for this specific use case&amp;hellip;&lt;/p&gt;

&lt;h2&gt;A second a approach&amp;hellip;&lt;/h2&gt;

&lt;p&gt;As we were working to create a &amp;ldquo;weekly notification with the received payments&amp;rdquo;&amp;hellip;&lt;/p&gt;

&lt;p&gt;&amp;hellip; We thought, that we could move the construction of the range to a new object and then ask
&lt;code&gt;Payment&lt;/code&gt; for the payments received between that range.&lt;/p&gt;

&lt;p&gt;So we end with something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And later we add a &lt;code&gt;for_today&lt;/code&gt; method&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&lt;/span&gt;
&lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is more verbose, but at least for us, the intent was a little more clear.&lt;/p&gt;

&lt;p&gt;So we wrote some tests for the &lt;code&gt;WeeklyNotificationRange&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;".for_date"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"on friday"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"on thursday"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&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="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;".for_today"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns range for current day"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Extracting this method help us to express more clearly in the specs how the range will change if we ask for a different date. I think that this was a win!&lt;/p&gt;

&lt;p&gt;Then we wrote the implementation (Well, we just moved it)&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&lt;/span&gt;
    &lt;span class="n"&gt;for_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We changed the &lt;code&gt;Payment&lt;/code&gt; specs&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;".received_between"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received on last second"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received one second late"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received in the first second"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="s2"&gt;"payment received on previous week"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;described_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_empty&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the implementation&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment&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="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;received_at: &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;We stopped here&amp;hellip;&lt;/h2&gt;

&lt;p&gt;We decided to let the code like this, because we thought it was clear enough&amp;hellip;&lt;/p&gt;

&lt;p&gt;We used this code to run a rake task that sends an email to each user that received a payment in the last week&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friday?&lt;/span&gt;
  &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&lt;/span&gt;

  &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;extract_users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_payments_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in the mailer we did something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;received_payments_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&lt;/span&gt;
    &lt;span class="vi"&gt;@payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# mail(...)&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;h2&gt;But we could have abstracted a little more&amp;hellip;&lt;/h2&gt;

&lt;p&gt;Again, for us this was enough, but&amp;hellip;&lt;/p&gt;

&lt;p&gt;I want to share with you how you can encapsulate something like this a little more,
because maybe it could be useful in some circumstances&amp;hellip;&lt;/p&gt;

&lt;p&gt;We can add a &lt;code&gt;Payment::WeeklyNotification&lt;/code&gt; to handle the tasks related with this use case&amp;hellip;&lt;/p&gt;

&lt;p&gt;For example in our rake task we can do something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friday?&lt;/span&gt;
  &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_payments_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friday?&lt;/span&gt;
  &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or maybe just&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friday?&lt;/span&gt;
  &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotification&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="nf"&gt;notify_users&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in the mailer we can do something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;received_payments_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotification&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="nf"&gt;payments_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# mail(...)&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;p&gt;And move the logic to that object&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeeklyNotification&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;users&lt;/span&gt;
    &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_range&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;extract_users&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_users&lt;/span&gt;
    &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_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;user&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;email_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;deliver_later&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;email_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;UserMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;received_payments_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;payments_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;received_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;date_range&lt;/span&gt;
    &lt;span class="no"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WeeklyNotificationRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_today&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;p&gt;And, of course add some tests&amp;hellip; Or move them&amp;hellip;&lt;/p&gt;

&lt;h2&gt;But you should stop somewhere&amp;hellip;&lt;/h2&gt;

&lt;p&gt;But you should stop somewhere, and there are trade-offs in each decision&amp;hellip;&lt;/p&gt;

&lt;p&gt;No solution is perfect&amp;hellip; Each abstraction requires maintenance (tests, documentation, etc&amp;hellip;),
but if you avoid them the intent of the code could be less clear.&lt;/p&gt;

&lt;h2&gt;How could you know where to stop?&lt;/h2&gt;

&lt;p&gt;Is really hard to know, but I like the &lt;a href="https://martinfowler.com/bliki/BeckDesignRules.html"&gt;BeckDesignRules&lt;/a&gt; &amp;hellip;&lt;/p&gt;

&lt;p&gt;Kent Beck came up with his four rules of simple design while he was developing ExtremeProgramming in the late 1990&amp;rsquo;s. And Martin Fowler express them like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passes the tests&lt;/li&gt;
&lt;li&gt;Reveals intention&lt;/li&gt;
&lt;li&gt;No duplication&lt;/li&gt;
&lt;li&gt;Fewest elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rules are in priority order, so &amp;ldquo;passes the tests&amp;rdquo; takes priority over &amp;ldquo;reveals intention&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;You can use this rules as a guide =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a DateTime range to query for the records created a "week before"</title>
    <link rel="alternate" href="https://bhserna.com/query-date-ranges-rails-active-record"/>
    <id>https://bhserna.com/query-date-ranges-rails-active-record</id>
    <published>2021-05-06T00:28:00Z</published>
    <updated>2021-05-06T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you need to query for the records created in the &amp;ldquo;week before&amp;rdquo; of a given date.&lt;/p&gt;

&lt;p&gt;How would you do it?&lt;/p&gt;

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

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

&lt;p&gt;For this example a &amp;ldquo;week before&amp;rdquo; means that&amp;hellip;&lt;/p&gt;

&lt;p&gt;If the given date is the Friday &lt;code&gt;2021-04-30&lt;/code&gt; it will return the records created between the begining of the previous Friday &lt;code&gt;2021-04-23&lt;/code&gt; and the last second of the Thursday &lt;code&gt;2021-04-29&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Asking to ActiveRecord&lt;/h2&gt;

&lt;p&gt;In rails, with ActiveRecord, you can ask for the records created between a range with&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&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;created_at: &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will build a &lt;code&gt;BETWEEN&lt;/code&gt; query with the range you have provided, but how do you build the range?&amp;hellip;&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s see!&lt;/p&gt;

&lt;h2&gt;Building the range&lt;/h2&gt;

&lt;p&gt;Let&amp;rsquo;s start with the Friday &lt;code&gt;2021-04-30&lt;/code&gt; from the example&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&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;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 30 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we substract &lt;code&gt;7&lt;/code&gt; to it, it will return the previous Friday&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will do the same if we substract &lt;code&gt;7.days&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What happen if we substract &lt;code&gt;7.days.ago&lt;/code&gt;?&amp;hellip;&lt;/p&gt;

&lt;p&gt;It will return an error because &lt;code&gt;7.days.ago&lt;/code&gt; is not a number&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;
&lt;span class="c1"&gt;# Traceback (most recent call last):&lt;/span&gt;
&lt;span class="c1"&gt;#         1: from (irb):4&lt;/span&gt;
&lt;span class="c1"&gt;# TypeError (expected numeric)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is actually a &lt;code&gt;Datetime&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021 10:22:54 CDT -05:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can convert it to a date with &lt;code&gt;to_date&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_date&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But let&amp;rsquo;s return to the range&amp;hellip; &lt;/p&gt;

&lt;p&gt;Now to get the Thursday &lt;code&gt;2021-04-29&lt;/code&gt; we need to substract &lt;code&gt;1.day&lt;/code&gt; to our &lt;code&gt;date&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Thu, 29 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, to build the desired range we can do&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021..Thu, 29 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or (without &lt;code&gt;days&lt;/code&gt; and &lt;code&gt;day&lt;/code&gt;)&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021..Thu, 29 Apr 2021&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That will return a range of &lt;code&gt;Date&lt;/code&gt; objects&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [Fri, 23 Apr 2021, Sat, 24 Apr 2021, Sun, 25 Apr 2021, Mon, 26 Apr 2021, Tue, 27 Apr 2021, Wed, 28 Apr 2021, Thu, 29 Apr 2021, Fri, 30 Apr 2021]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But normally our &lt;code&gt;datetime&lt;/code&gt; fields store the information for &lt;code&gt;utc&lt;/code&gt;. So, we should not query directly with the dates, because it
won&amp;rsquo;t match the expected timezone.&lt;/p&gt;

&lt;p&gt;We need to build the range aware of the time zone that we need and then let rails transform the range to utc.&lt;/p&gt;

&lt;p&gt;To do that we can use the rails methods &lt;code&gt;beginning_of_day&lt;/code&gt; and &lt;code&gt;end_of_day&lt;/code&gt; to convert our date in to a &lt;code&gt;datetime&lt;/code&gt; aware of the time zone.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;beginning_of_day&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end_of_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Fri, 23 Apr 2021 00:00:00 CDT -05:00..Fri, 30 Apr 2021 23:59:59 CDT -05:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now that we have the range that we want we can pass it to the rails &lt;code&gt;where&lt;/code&gt; method&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Record&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;created_at: &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Try this code in the console&lt;/h2&gt;

&lt;p&gt;To understand this code better, try to run each example in the rails console and to test the things that maybe are not so clear at this moment.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to read the noCertificado from a CSD for a mexican CFDI</title>
    <link rel="alternate" href="https://bhserna.com/csd-nocertificado-ruby"/>
    <id>https://bhserna.com/csd-nocertificado-ruby</id>
    <published>2021-05-05T00:28:00Z</published>
    <updated>2021-05-05T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;This was new for me, I was looking how to get the &lt;code&gt;noCertificado&lt;/code&gt; from a &lt;code&gt;CSD&lt;/code&gt;
(Certificado de Sello Digital) and I found it is the &lt;code&gt;serial&lt;/code&gt; of the
certificate but in base 2.&lt;/p&gt;

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

&lt;p&gt;In ruby you can get it with a &lt;a href="https://ruby-doc.org/stdlib-3.0.0/libdoc/openssl/rdoc/OpenSSL/X509/Certificate.html"&gt;OpenSSL::X509::Certificate&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my_cert.cer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;X509&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you ask the &lt;code&gt;serial&lt;/code&gt; you will get an &lt;a href="https://ruby-doc.org/stdlib-3.0.0/libdoc/openssl/rdoc/OpenSSL/BN.html"&gt;OpenSSL::BN&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serial&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; an OpenSSL::BN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then you can ask the for the &lt;a href="https://ruby-doc.org/stdlib-3.0.0/libdoc/openssl/rdoc/OpenSSL/BN.html#method-i-to_s"&gt;to_s&lt;/a&gt; of that object. By default it will return it with base 10&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serial&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;hellip;but you can ask for it in base 2, this is the &lt;code&gt;noCertificado&lt;/code&gt; =)&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serial&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "noCertificato" value...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>How to test a PDF download with capybara</title>
    <link rel="alternate" href="https://bhserna.com/test-pdf-download-capybara"/>
    <id>https://bhserna.com/test-pdf-download-capybara</id>
    <published>2021-05-03T00:28:00Z</published>
    <updated>2021-05-03T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you want to test a PDF download with capybara, but don&amp;rsquo;t know how to do it?&lt;/p&gt;

&lt;p&gt;Here I will how you can doing it are using the default driver (not the javascript driver).&lt;/p&gt;

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

&lt;h2 id="what"&gt;What do you want to test?&lt;/h2&gt;

&lt;p&gt;The first thing that you are going to need, is knowing what do you want to test, here I will show you how you can test&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That you are downloding the expected &lt;code&gt;content&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;with the right &lt;code&gt;filename&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the right &lt;code&gt;content_type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and that &lt;code&gt;disposition&lt;/code&gt; is the one that you actually want&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;hellip; but maybe some of them are not important for your specific use case, so you can change the code to fit your needs.&lt;/p&gt;

&lt;h2 id="content"&gt;How to test if the content is right&lt;/h2&gt;

&lt;p&gt;You can compare the &lt;code&gt;page.body&lt;/code&gt; with the content of the file that you want to send.&lt;/p&gt;

&lt;p&gt;For example if you are using &lt;code&gt;carrierwave&lt;/code&gt; and have a &lt;code&gt;Receipt&lt;/code&gt; record like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Receipt&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="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="n"&gt;mount_uploader&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PdfUploader&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can send the pdf like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReceiptsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;send_data&lt;/span&gt; &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;filename: &lt;/span&gt;&lt;span class="s2"&gt;"Receipt-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;disposition: &lt;/span&gt;&lt;span class="s2"&gt;"inline"&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;p&gt;You can write a test like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User downloads receipt pdf"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"download from the receipts page"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;receipts_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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;h2 id="filename"&gt;How to test if the filename is right&lt;/h2&gt;

&lt;p&gt;To test the &lt;code&gt;filename&lt;/code&gt; you can check the  &lt;code&gt;Content-Disposition&lt;/code&gt; header, because it has the form&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#{disposition}; filename=#{filename}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User downloads receipt pdf"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"download from the receipts page"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;receipts_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s2"&gt;"Receipt-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="content_type"&gt;How to test if the content type is right&lt;/h2&gt;

&lt;p&gt;To test &lt;code&gt;content_type&lt;/code&gt; you can check the &lt;code&gt;Content-Type&lt;/code&gt; header, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User downloads receipt pdf"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"download from the receipts page"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;receipts_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"application/pdf"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="disposition"&gt;How to test if the disposition is right&lt;/h2&gt;

&lt;p&gt;To test the &lt;code&gt;disposition&lt;/code&gt; you also can check the  &lt;code&gt;Content-Disposition&lt;/code&gt; header, because it has the form&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#{disposition}; filename=#{filename}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User downloads receipt pdf"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"download from the receipts page"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;receipts_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Content-Disposition"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="s2"&gt;"inline"&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;h2 id="all_together"&gt;A helper to test all together&lt;/h2&gt;

&lt;p&gt;If you want to test the four things together (or some of them), you can write a helper function to express that you want a download with some characteristics. You can do something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"User downloads receipt pdf"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="s2"&gt;"download from the receipts page"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;login_as&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;

    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;receipts_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download"&lt;/span&gt;

    &lt;span class="n"&gt;expect_download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;content: &lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;filename: &lt;/span&gt;&lt;span class="s2"&gt;"Receipt-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;content_type: &lt;/span&gt;&lt;span class="s2"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;disposition: &lt;/span&gt;&lt;span class="s2"&gt;"inline"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expect_download&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="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response_headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Disposition'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; filename=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;content&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;p&gt;&amp;hellip; and that&amp;rsquo;s all for now. I hope it helps =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A simple way of testing a CSV download with capybara</title>
    <link rel="alternate" href="https://bhserna.com/test-csv-download-capybara"/>
    <id>https://bhserna.com/test-csv-download-capybara</id>
    <published>2021-04-20T00:28:00Z</published>
    <updated>2021-04-20T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you need/want to test a CSV download with capybara, but don&amp;rsquo;t know how to do it?&lt;/p&gt;

&lt;p&gt;Here I will show you a simple way of doing it if you are using the default driver.&lt;/p&gt;

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

&lt;h2 id="example"&gt;CSV example&lt;/h2&gt;

&lt;p&gt;Imagine that in your feature you are exporting a csv like this one&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;User, Email, Amount
Juan, juan@example.com, $10.00
Pedro, pedro@example.com, $20.00
#...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="reading_csv"&gt;Reading the csv&lt;/h2&gt;

&lt;p&gt;If you are sending the csv, with &lt;code&gt;send_file&lt;/code&gt; or &lt;code&gt;send_data&lt;/code&gt; in the controller, you can access the csv with &lt;code&gt;page.body&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And you can get the &lt;code&gt;CSV&lt;/code&gt; as an array, by parsing the &lt;code&gt;page.body&lt;/code&gt; like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="data_representation"&gt;How to represent the csv data?&lt;/h2&gt;

&lt;p&gt;One way to represent the data in a readable way for your tests is to &amp;ldquo;transpose&amp;rdquo; the csv data to show the columns as rows, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Juan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Pedro"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"juan@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pedro@example.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$10.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$20.00"&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;p&gt;And to do this, you can call the &lt;code&gt;transpose&lt;/code&gt; method&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;transpose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="test"&gt;A test example&lt;/h2&gt;

&lt;p&gt;Putting it all together, a test could look like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feature&lt;/span&gt; &lt;span class="s2"&gt;"My feature"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;page_with_csv_button_path&lt;/span&gt;

    &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Download csv"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Juan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Pedro"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"juan@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pedro@example.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$10.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$20.00"&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csv&lt;/span&gt;
    &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;transpose&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;p&gt;And that&amp;rsquo;s all!&amp;hellip; I hope it helps =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A "read-more" behavior truncating with rails truncate helper and Stimulus.js</title>
    <link rel="alternate" href="https://bhserna.com/read-more-rails-truncate-stimulus"/>
    <id>https://bhserna.com/read-more-rails-truncate-stimulus</id>
    <published>2021-04-19T00:28:00Z</published>
    <updated>2021-04-19T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;A code sample of a &amp;ldquo;read-more&amp;rdquo; behavior using the rails &lt;code&gt;truncate&lt;/code&gt; method and Stimulus.js.&lt;/p&gt;

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

&lt;h2 id="example"&gt;The example&lt;/h2&gt;

&lt;iframe class="w-100 ba b--light-blue" style="height: 300px" src="read-more-rails-truncate-stimulus/example.html"&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href="read-more-rails-truncate-stimulus/example.html"&gt;Visit example page&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="html_and_css"&gt;The html and css&lt;/h2&gt;

&lt;p&gt;Your are going to need&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;fullContentValue&lt;/code&gt; with the full content.&lt;/li&gt;
&lt;li&gt;A class to &lt;code&gt;hide&lt;/code&gt; the buttons.&lt;/li&gt;
&lt;li&gt;3 targets, the &lt;code&gt;content&lt;/code&gt;, the &lt;code&gt;moreButton&lt;/code&gt; and the &lt;code&gt;lessButton&lt;/code&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;2 actions

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showMore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showLess&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nc"&gt;.hide&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"read-more"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-full-content-value=&lt;/span&gt;&lt;span class="s"&gt;"This is the full content without truncation"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-hide-class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    This is the truncated....
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"moreButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showMore"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show more
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"lessButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showLess"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show less
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To truncate the content, you can use the &lt;code&gt;truncate&lt;/code&gt; &lt;a href="https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-truncate"&gt;helper&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Once upon a time in a world far far away"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="javascript"&gt;The javascript&lt;/h2&gt;

&lt;p&gt;Here you are going to toggle the truncated content and the full content as needed.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ReadMoreController&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"moreButton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lessButton"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hide"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fullContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncatedContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateContent&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullContentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&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="nx"&gt;showMore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullContentValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&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;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Other ways of doing it&amp;hellip;&lt;/h2&gt;

&lt;p&gt;If instead of number characters or words you need to truncate your content by number of lines, you can try these examples.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="truncating-multiple-line-text-read-more.html"&gt;With custom javascript and Stimulus.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="read-more-line-clamp-stimulus.html"&gt;With line-clamp and Stimulus.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>A "read-more" behavior truncating by number of lines with custom javascript and Stimulus.js</title>
    <link rel="alternate" href="https://bhserna.com/truncating-multiple-line-text-read-more"/>
    <id>https://bhserna.com/truncating-multiple-line-text-read-more</id>
    <published>2021-04-02T00:28:00Z</published>
    <updated>2021-04-02T00:28:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Are you looking for a way of implementing a &amp;ldquo;read-more&amp;rdquo; behavior truncating by the number of lines instead of the number of words?&lt;/p&gt;

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

&lt;p&gt;If this is your case, this article can help you =) &amp;hellip;&lt;/p&gt;

&lt;h2 id="example"&gt;The example&lt;/h2&gt;

&lt;iframe class="w-100 ba b--light-blue" style="height: 300px" src="truncating-multiple-line-text-read-more/example.html"&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;a href="truncating-multiple-line-text-read-more/example.html"&gt;Visit example page&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="html_and_css"&gt;The html and css&lt;/h2&gt;

&lt;p&gt;Your are going to need&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;linesValue&lt;/code&gt; to configure the number of lines that we want to display,&lt;/li&gt;
&lt;li&gt;A class to hide the buttons&amp;hellip;&lt;/li&gt;
&lt;li&gt;3 targets, the &lt;code&gt;content&lt;/code&gt;, the &lt;code&gt;moreButton&lt;/code&gt; and the &lt;code&gt;lessButton&lt;/code&gt;&amp;hellip;&lt;/li&gt;
&lt;li&gt;3 actions

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resize@window-&amp;gt;read-more#render&lt;/code&gt; - to calculate the truncation on rezise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showMore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;click-&amp;gt;read-more#showLess&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="highlight css"&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.hide&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"read-more"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-lines-value=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;
     &lt;span class="na"&gt;data-read-more-hide-class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
     &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"resize@window-&amp;gt;read-more#render"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    The content that you want to truncate...
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"moreButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showMore"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show more
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hide"&lt;/span&gt;
    &lt;span class="na"&gt;data-read-more-target=&lt;/span&gt;&lt;span class="s"&gt;"lessButton"&lt;/span&gt;
    &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"read-more#showLess"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Show less
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="javascript"&gt;The javascript&lt;/h2&gt;

&lt;p&gt;On &lt;code&gt;render&lt;/code&gt; you are going to truncate the content, to the configured &lt;code&gt;linesValue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But you are going to do it just if the &lt;code&gt;height&lt;/code&gt; is greater that the &lt;code&gt;expectedHeigt&lt;/code&gt; that is the product of the &lt;code&gt;linesValue&lt;/code&gt; with the &lt;code&gt;lineHeight&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight javascript"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ReadMoreController&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"moreButton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lessButton"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hide"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&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="nx"&gt;showMore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;showLess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;truncateContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lessButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moreButtonTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;showAllContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsList&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addWordToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;truncateContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calculateWordsToDisplayWhenTruncated&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderTrucatedContentWithEllipsis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;calculateWordsToDisplayWhenTruncated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsToDisplayWhenTrucated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsList&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsToDisplayWhenTrucated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addWordToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&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="nx"&gt;renderTrucatedContentWithEllipsis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsToDisplayWhenTrucated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wordsToDisplayWhenTrucated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addWordToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renderTrucatedContentWithEllipsis&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="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hideClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;removeContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;addWordToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;addToContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;expectedHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;linesValue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;wordsList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&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;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Other ways of doing it&amp;hellip;&lt;/h2&gt;

&lt;p&gt;If you are looking for a way to do the truncation using css &lt;code&gt;line-clamp&lt;/code&gt;, you take a look to &lt;a href="read-more-line-clamp-stimulus.html"&gt;this example using &amp;ldquo;line-clamp&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you can truncate by number of characters, you can check &lt;a href="read-more-rails-truncate-stimulus.html"&gt;this example using the &amp;ldquo;truncate&amp;rdquo; helper.&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Capybara cheatsheet</title>
    <link rel="alternate" href="https://bhserna.com/capybara-cheatsheet"/>
    <id>https://bhserna.com/capybara-cheatsheet</id>
    <published>2021-03-24T01:53:00Z</published>
    <updated>2021-03-24T01:53:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Are you working with capybara?&amp;hellip; Are you constantly searching for capybara helpers?&lt;/p&gt;

&lt;p&gt;Here is a small reference with some of the most used methods/helpers.&lt;/p&gt;

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

&lt;h2 id="navigation"&gt;Navigation&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Navigate to a particular path&lt;/span&gt;
&lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="n"&gt;blogs_path&lt;/span&gt;
&lt;span class="n"&gt;visit&lt;/span&gt; &lt;span class="s2"&gt;"/blog"&lt;/span&gt;

&lt;span class="c1"&gt;# Click on an anchor tag, button, or input with type submit&lt;/span&gt;
&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"Save Project"&lt;/span&gt;
&lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s2"&gt;"New Project"&lt;/span&gt;

&lt;span class="c1"&gt;# Click an anchor tag&lt;/span&gt;
&lt;span class="n"&gt;click_link&lt;/span&gt; &lt;span class="s1"&gt;'New Project'&lt;/span&gt;

&lt;span class="c1"&gt;# Click a button, or input with type submit&lt;/span&gt;
&lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'Save'&lt;/span&gt;

&lt;span class="c1"&gt;# Find and click a Capybara::Element&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a.title"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="form_interaction"&gt;Form interaction&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Fill input fields (text, textarea, number, phone, etc...)&lt;/span&gt;
&lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'Title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="s1"&gt;'I love Rails!'&lt;/span&gt;
&lt;span class="n"&gt;fill_in&lt;/span&gt; &lt;span class="s1"&gt;'post[title]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="s1"&gt;'I love Rails!'&lt;/span&gt;

&lt;span class="c1"&gt;# Checkbox&lt;/span&gt;
&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="s1"&gt;'I accept the terms and conditions'&lt;/span&gt;
&lt;span class="n"&gt;uncheck&lt;/span&gt; &lt;span class="s1"&gt;'I accept the terms and conditions'&lt;/span&gt;

&lt;span class="c1"&gt;# Radio button&lt;/span&gt;
&lt;span class="n"&gt;choose&lt;/span&gt; &lt;span class="s1"&gt;'Female'&lt;/span&gt;

&lt;span class="c1"&gt;# Select option from select tag&lt;/span&gt;
&lt;span class="nb"&gt;select&lt;/span&gt; &lt;span class="s1"&gt;'MA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="s1"&gt;'State'&lt;/span&gt;

&lt;span class="c1"&gt;# File input&lt;/span&gt;
&lt;span class="n"&gt;attach_file&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'spec/fixture/some_file.png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Click a button, or input with type submit&lt;/span&gt;
&lt;span class="n"&gt;click_button&lt;/span&gt; &lt;span class="s1"&gt;'Save'&lt;/span&gt;

&lt;span class="c1"&gt;# Accept a confirm dialog&lt;/span&gt;
&lt;span class="n"&gt;accept_confirm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;click_on&lt;/span&gt; &lt;span class="s1"&gt;'Destroy'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="page_interaction_and_scoping"&gt;Page interaction and scoping&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Returns a single Capybara::Node::Element instance from the page.&lt;/span&gt;
&lt;span class="c1"&gt;# It will wait for an element to appear on the page.&lt;/span&gt;
&lt;span class="c1"&gt;# If the element does not appear it will raise an error.&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.todos li:first-child'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Returns an array of Capybara::Node::Element instances from the page.&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.todos li:nth-of-type(odd)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Within will scope interaction to within a particular selector.&lt;/span&gt;
&lt;span class="n"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'footer'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Copyright'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Is the selector present on the page?&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_css?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nav[data-role="user-menu"] li'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'Profile'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the content present on the page?&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_content?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Sign in'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="matchers"&gt;Matchers&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Is the content present on the page?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Some Content"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_no_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Some Content"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the selector present on the page?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input#post_title"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input#post_title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"Capybara cheatsheet"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the selector present X times on the page?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;maximum: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;minimum: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;between: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the selector with the right text on the page?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"p a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"p a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="sr"&gt;/[hH]ello(.+)/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the field present?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s2"&gt;"Rambo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="s2"&gt;"Rambo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;disabled: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the link present?&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;href: &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_no_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;href: &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="node_interaction"&gt;Node interaction&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Trigger a click on a Capybara::Element&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a.title"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;

&lt;span class="c1"&gt;# Trigger allow triggering a custom event.&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a.title"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"focus"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Is the element visible?&lt;/span&gt;
&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".navigation"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;visible?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="debugging"&gt;Debugging&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Save the current page and attempt to open the HTML&lt;/span&gt;
&lt;span class="c1"&gt;# in the default web browser.&lt;/span&gt;
&lt;span class="n"&gt;save_and_open_page&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="references"&gt;References&lt;/h2&gt;

&lt;p&gt;This cheatsheet is just a mix of other very usefull cheatsheets and other references.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node"&gt;Capybara::Node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Session"&gt;Capybara::Session&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rubydoc.info/github/jnicklas/capybara/Capybara%2FSession%3Aaccept_confirm"&gt;Capybara::Session#accept confirm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/upcase/test-driven-rails-resources/capybara.pdf"&gt;Thoughtbot&amp;rsquo;s Capybara Cheatsheet from upcase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/tomas-stefano/6652111"&gt;tomas-stefamo/Capybara.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Get the cheatsheet on PDF&lt;/h2&gt;

&lt;p&gt;If you work with capybara and you are constantly searching for capybara
helpers, maybe to have this little cheatsheet at hand could work for you.&lt;/p&gt;

&lt;p&gt;&lt;script async data-uid="9b5d718a7e" src="https://bhserna.ck.page/9b5d718a7e/index.js"&gt;&lt;/script&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Custom capybara matchers with compound expectations</title>
    <link rel="alternate" href="https://bhserna.com/custom-capybara-matchers-with-compound-expectations"/>
    <id>https://bhserna.com/custom-capybara-matchers-with-compound-expectations</id>
    <published>2021-03-17T13:12:00Z</published>
    <updated>2021-03-17T13:12:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Imagine that you want to refactor this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[data-role=description]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"Desc 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[data-role=amount]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s2"&gt;"$250.00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;into this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;display_entry_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Desc1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vg"&gt;$250&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;What would you do?&amp;hellip;&lt;/p&gt;

&lt;p&gt;Maybe you can write a custom matcher, but at least for me, in cases like this, it just feel like too much.&lt;/p&gt;

&lt;p&gt;One thing that I think is simpler is to use rspec&amp;rsquo;s &lt;a href="https://relishapp.com/rspec/rspec-expectations/docs/compound-expectations"&gt;compound expectations&lt;/a&gt;, to chain all the expectations that you want, and return a matcher.&lt;/p&gt;

&lt;p&gt;Something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;display_entry_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[data-role=description]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;and&lt;/span&gt; &lt;span class="n"&gt;have_css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[data-role=amount]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;amount&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;p&gt;I have found this little trick very usefull, I hope it can help you too.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Quiz to practice the difference between preload, includes or eager_load</title>
    <link rel="alternate" href="https://bhserna.com/quiz-to-practice-the-difference-between-preload-includes-or-eager_load"/>
    <id>https://bhserna.com/quiz-to-practice-the-difference-between-preload-includes-or-eager_load</id>
    <published>2021-02-22T13:05:00Z</published>
    <updated>2021-02-22T13:05:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you need more practice to understand &lt;a href="https://bhserna.com/includes-preload-eager-load-joins-in-rails.html"&gt;the difference between &amp;ldquo;includes&amp;rdquo;, &amp;ldquo;preload&amp;rdquo;, &amp;ldquo;eager_load&amp;rdquo; and &amp;ldquo;joins&amp;rdquo;&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Here you will find a set of exercises to help you understand how you can use this 4 methods.&lt;/p&gt;

&lt;p&gt;You can find the code on &lt;a href="https://github.com/bhserna/preloading_quiz"&gt;bhserna/preloading_quiz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is are the exercises for the post &lt;a href="https://bhserna.com/examples-to-learn-the-difference-between-preload-includes-eager-load.html"&gt;Examples to learn the difference between preload, includes or eager_load&lt;/a&gt;, you can find the answers there.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;/exercises&lt;/code&gt; you will find a list of exercises with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The description of a task&lt;/li&gt;
&lt;li&gt;And a function to write the results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should provide a way to fetch the records.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"-----------------------------"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"TASK"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"1. Fetch all acomodations preloading rooms (with includes)"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"-----------------------------"&lt;/span&gt;

&lt;span class="c1"&gt;# accomodations = ?&lt;/span&gt;
&lt;span class="c1"&gt;# display_with_rooms(accomodations)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;How to run the examples&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a postgres database&lt;/strong&gt; with &lt;code&gt;createdb preloading_quiz&lt;/code&gt;. As
you can see the in &lt;code&gt;db/config.rb&lt;/code&gt; the name of the database is hardcoded, so
you will need to create a database with that name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install the dependencies&lt;/strong&gt; with &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the seeds&lt;/strong&gt; with &lt;code&gt;ruby db/seeds.rb&lt;/code&gt;. You can also update the data that
you want to use, there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the exercises&lt;/strong&gt; with &lt;code&gt;ruby exercises/01.rb&lt;/code&gt;, etc&amp;hellip;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Which specs would you prefer for this presentation object?</title>
    <link rel="alternate" href="https://bhserna.com/which-specs-would-you-prefer-for-this-presentation-object"/>
    <id>https://bhserna.com/which-specs-would-you-prefer-for-this-presentation-object</id>
    <published>2021-02-17T16:36:00Z</published>
    <updated>2021-02-17T16:36:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;I am currently working on a kind of &amp;ldquo;presentation object&amp;rdquo; that I called &lt;code&gt;IdealPayments::GlobalStats&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This object will be used in two places, to display a graph and to show the data in a page where we can see all the &amp;ldquo;ideal payments&amp;rdquo;.&lt;/p&gt;

&lt;p&gt;In my imagination and implementation, this object will depenend on an active record relation and chain the right methods to present the result.&lt;/p&gt;

&lt;p&gt;Although I think this is not a big deal, I would like to know what do you think is best way to write unit/micro tests for this object and why?&lt;/p&gt;

&lt;p&gt;I have two options&amp;hellip;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test through the database as if the source (the active record relation) was invisible.&lt;/li&gt;
&lt;li&gt;Test by allowing that &amp;ldquo;the right messages&amp;rdquo; will be send to the &amp;ldquo;source&amp;rdquo; and expect the right result is returned.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: I already have test all the methods on the active record class through the database.&lt;/p&gt;

&lt;h2&gt;Option 1 - Test through the database as if the source (the active record relation) was invisible.&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;IdealPayments&lt;/span&gt;
  &lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;GlobalStats&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#generated_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the sum of the ideal payments amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2_000&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is just for projects promoted by us"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;dev_project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;dev_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;promoter: &lt;/span&gt;&lt;span class="s2"&gt;"other"&lt;/span&gt;
        &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;dev_project: &lt;/span&gt;&lt;span class="n"&gt;dev_project&lt;/span&gt;
        &lt;span class="n"&gt;ideal_payments_for_project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payments_for_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;project: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;

        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ideal_payments_for_project: &lt;/span&gt;&lt;span class="n"&gt;ideal_payments_for_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2_000&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#paid_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the sum of the ideal payments paid amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paid_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#pending_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the difference between the generated amount and the paid amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2_000&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#pending_amount_without_delay"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is not yet delayed"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending_amount_without_delay&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is delayed"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_less_than_30_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is delayed less than 30 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_less_than_30_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_between_30_and_90_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is delayed more than 30 days but less than 90 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_between_30_and_90_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_more_90_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is delayed more than than 90 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ideal_payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;paid_amount: &lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;amount: &lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_more_90_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Option 2. Test by allowing that &amp;ldquo;the right messages&amp;rdquo; will be send to the &amp;ldquo;source&amp;rdquo; and expect the right result is returned.&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;IdealPayments&lt;/span&gt;
  &lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;GlobalStats&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#source"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is by default the ideal payments for projects promoted by us"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ideal payments for project promoted by us"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;IdealPayment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:for_projects_promoted_by_us&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#generated_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generated_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#paid_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total paid amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_paid_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paid_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#pending_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total paid amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#pending_amount_without_delay"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total pending amount of the not delayed payments"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive_message_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_delayed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending_amount_without_delay&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total pending amount of the delayed payments"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive_message_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delayed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_less_than_30_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total pending amount that is delayed less than 30 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;delayed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Delayed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delayed_less_than&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_less_than_30_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_between_30_and_90_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the total pending amount that is delayed more than 30 days but less than 90 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;delayed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Delayed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delayed_between&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_between_30_and_90_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"#delayed_amount_more_90_days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is the pending amount that is delayed more than than 90 days"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;delayed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Delayed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:delayed_more_than&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:total_pending_amount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_amount_more_90_days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Here is the actual implementation&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;IdealPayments&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalStats&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ideal_payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;IdealPayment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_projects_promoted_by_us&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@ideal_payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ideal_payments&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;source&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generated_amount&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paid_amount&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_paid_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pending_amount&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pending_amount_without_delay&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not_delayed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delayed_amount&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delayed_amount_less_than_30_days&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_less_than&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delayed_amount_between_30_and_90_days&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delayed_amount_more_90_days&lt;/span&gt;
      &lt;span class="n"&gt;ideal_payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delayed_more_than&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;total_pending_amount&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:ideal_payments&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;
</content>
  </entry>
  <entry>
    <title>Code to run the examples for preload, includes or eager_load</title>
    <link rel="alternate" href="https://bhserna.com/preload-includes-eager-load-examples-code"/>
    <id>https://bhserna.com/preload-includes-eager-load-examples-code</id>
    <published>2021-02-16T00:18:00Z</published>
    <updated>2021-02-16T00:18:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you know &amp;ldquo;in theory&amp;rdquo; &lt;a href="https://bhserna.com/includes-preload-eager-load-joins-in-rails.html"&gt;the difference between &amp;ldquo;includes&amp;rdquo;, &amp;ldquo;preload&amp;rdquo;, &amp;ldquo;eager_load&amp;rdquo; and &amp;ldquo;joins&amp;rdquo;&lt;/a&gt;, but you still think that you need more examples to really understand how to use them?&lt;/p&gt;

&lt;p&gt;Here you will find a set of examples to help you understand how you can use this 4 methods.&lt;/p&gt;

&lt;p&gt;You can find the code on &lt;a href="https://github.com/bhserna/preloading_examples"&gt;bhserna/preloading_examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the code that I used for the post: &lt;a href="https://bhserna.com/examples-to-learn-the-difference-between-preload-includes-eager-load.html"&gt;Examples to learn the difference between preload, includes or eager_load&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;lib/examples.rb&lt;/code&gt; you will find a list of examples with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The description of a task&lt;/li&gt;
&lt;li&gt;The query written with ActiveRecord&lt;/li&gt;
&lt;li&gt;A function to write the results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use this code to play a little more and try different things.&lt;/p&gt;

&lt;h2&gt;How to run the examples&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a postgres database&lt;/strong&gt; with &lt;code&gt;createdb preloading_examples&lt;/code&gt;. As
you can see the in &lt;code&gt;db/config.rb&lt;/code&gt; the name of the database is hardcoded, so
you will need to create a database with that name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install the dependencies&lt;/strong&gt; with &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the seeds&lt;/strong&gt; with &lt;code&gt;ruby db/seeds.rb&lt;/code&gt;. You can also update the data that
you want to use, there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run the examples&lt;/strong&gt; with &lt;code&gt;ruby lib/examples.rb&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Examples to learn the difference between preload, includes or eager_load</title>
    <link rel="alternate" href="https://bhserna.com/examples-to-learn-the-difference-between-preload-includes-eager-load"/>
    <id>https://bhserna.com/examples-to-learn-the-difference-between-preload-includes-eager-load</id>
    <published>2021-02-04T00:18:00Z</published>
    <updated>2021-02-04T00:18:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you know &amp;ldquo;in theory&amp;rdquo; &lt;a href="/includes-preload-eager-load-joins-in-rails.html"&gt;the difference between &amp;ldquo;includes&amp;rdquo;, &amp;ldquo;preload&amp;rdquo;, &amp;ldquo;eager_load&amp;rdquo; and &amp;ldquo;joins&amp;rdquo;&lt;/a&gt;, but you still think that you need more examples to really understand how to use them?&lt;/p&gt;

&lt;p&gt;Here you will find a set of examples to help you understand how you can use this 4 methods.&lt;/p&gt;

&lt;p&gt;For each example you will find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The description of a task&lt;/li&gt;
&lt;li&gt;The query written with ActiveRecord&lt;/li&gt;
&lt;li&gt;The produced SQL query&lt;/li&gt;
&lt;li&gt;The result with the fetched records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each example/excercise try read the description the task and try to think a little before reading the answers.&lt;/p&gt;

&lt;h2&gt;The model of the example&lt;/h2&gt;

&lt;p&gt;All the examples are based on the next model&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;version: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;accomodations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if_not_exists: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:bathrooms_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:decimal&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:guests_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;rooms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if_not_exists: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:accomodation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:beds_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;if_not_exists: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:accomodation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:stars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&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;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Accomodation&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;rooms&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;reviews&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;Room&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;Base&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;Review&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;Base&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Examples&lt;/h2&gt;

&lt;p&gt;&lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;1. Fetch all acomodations preloading rooms (with includes)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae488a20 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fae398408 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathroms_count: nil, guests_count: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae392fd0 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fae392968 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae3926c0 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae392238 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae3920f8 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae391b08 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;2. Fetch all acomodations preloading rooms (with preload)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb28541a0 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fb284fec0 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb284fda8 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fb284fbc8 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb284fab0 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fb284f9c0 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb284f790 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fb284f538 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;3. Fetch all acomodations preloading rooms (with eager load)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"beds_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae7b0e00 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fae7b0b80 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae7b0108 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fae7abd88 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae7ab5e0 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae7ab270 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae7aaaf0 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae7aa7f8 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;4. Fetch all acomodations preloading reviews (with includes)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae75b018 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae721160 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae721070 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae720cd8 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae720be8 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae720af8 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae720a08 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae720918 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae7207d8 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae7206e8 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae7205f8 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae720508 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;5. Fetch all acomodations preloading reviews (with preload)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6e8e00 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae6e8d10 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae6e8ba8 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6e8a18 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6e88b0 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6e86a8 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6e84c8 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6e82e8 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6e80b8 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6e3f90 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6e3ea0 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6e3d88 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;6. Fetch all acomodations preloading reviews (with eager_load)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"body"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6a9e80 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae6a9cf0 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae6a9a98 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6a9638 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6a9408 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6a9250 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6a8d00 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6a8b98 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6a8940 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6a8580 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae6a8328 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6a8170 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;7. Fetch all acomodations preloading reviews and rooms (with includes)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae66acd0 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae66ab40 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae66aa50 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae662d78 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fae662c88 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae66a938 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae66a848 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae66a6b8 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae662b70 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fae662a80 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae66a438 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae66a348 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae66a258 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae662990 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae662788 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae66a168 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae66a078 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae669f88 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae662648 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae662440 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;8. Fetch all acomodations preloading reviews and rooms (with preload)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae6035f8 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae603508 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae603418 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae5f9c60 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fae5f9af8 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae603328 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6031c0 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae6030d0 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae5f99b8 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fae5f98a0 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae602fe0 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae602ef0 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae602e00 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae5f97b0 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae5f9698 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae602d10 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae602b58 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae602950 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fae5f9580 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fae5f9468 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;9. Fetch all acomodations preloading reviews and rooms (with eager_load)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"body"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t2_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"beds_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae52b5e0 id: 3, accomodation_id: 1, body: "B3", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae52b108 id: 2, accomodation_id: 1, body: "B2", stars: 3&amp;gt;
#&amp;lt;Review:0x00007f7fae52ae60 id: 1, accomodation_id: 1, body: "B1", stars: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae52a488 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae529d58 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae529ad8 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae529218 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae528b38 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae5289a8 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae5280e8 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae523a20 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae5235e8 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;10. Fetch all acomodations for exactly 4 guests, preloading reviews (with includes)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;guests_count: &lt;/span&gt;&lt;span class="mi"&gt;4&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae4a3640 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae4a2f38 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae4a2830 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;11. Fetch all acomodations for exactly 4 guests, preloading reviews (with preload)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;guests_count: &lt;/span&gt;&lt;span class="mi"&gt;4&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae439678 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae4391c8 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae438f70 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;12. Fetch all acomodations for exactly 4 guests, preloading reviews (with eager_load)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;guests_count: &lt;/span&gt;&lt;span class="mi"&gt;4&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"body"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae3d2d88 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae3d25b8 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae3d1dc0 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;13. Fetch all acomodations with reviews of 4 stars (not in average), preloading reviews (with includes)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&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;reviews: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;stars: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"body"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"stars"&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae359a50 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae359708 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae359280 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae353808 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae3534c0 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;14. Fetch all acomodations with reviews of 4 stars (not in average), preloading reviews (with preload)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reviews&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;reviews: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;stars: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt; &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"stars"&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="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae27a828 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae279928 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae2737f8 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fae2721f0 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fae26a400 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fae269848 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;15. Fetch all acomodations with reviews of 4 stars (not in average), preloading reviews (with eager_load)&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&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;:reviews&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;reviews: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;stars: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SQL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;ms&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;"accomodations"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"bathrooms_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"guests_count"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t0_r3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&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;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"body"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1_r3&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"stars"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;"stars"&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fb28acee0 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb28accd8 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb28acb48 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fb28ac760 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb28ac580 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;16. List of accomodations with reviews of 4 stars (in average), preloading reviews&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:reviews&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;having&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"avg(reviews.stars) &amp;gt; 4"&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;:reviews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt; &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;HAVING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;"reviews"&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;"reviews"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fb28747e8 id: 10, accomodation_id: 4, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fb28746f8 id: 11, accomodation_id: 4, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb2874608 id: 12, accomodation_id: 4, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fb2875a80 id: 4, accomodation_id: 2, body: "B1", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb2874d38 id: 5, accomodation_id: 2, body: "B2", stars: 4&amp;gt;
#&amp;lt;Review:0x00007f7fb2874c48 id: 6, accomodation_id: 2, body: "B3", stars: 4&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Reviews
#&amp;lt;Review:0x00007f7fb2874b58 id: 7, accomodation_id: 3, body: "B1", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fb28749c8 id: 8, accomodation_id: 3, body: "B2", stars: 5&amp;gt;
#&amp;lt;Review:0x00007f7fb28748d8 id: 9, accomodation_id: 3, body: "B3", stars: 5&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="ba b--light-blue mb4"&gt;
  &lt;h3 class="f4 fw5 pa3 ma0 bg-washed-blue bb b--light-blue"&gt;17. List of accomodations with exactly 2 rooms, preloading rooms&lt;/h3&gt;
  &lt;div class="ph3"&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;ActiveRecord query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Accomodation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:rooms&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;having&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"count(rooms.id) = 2"&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;:rooms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;SQL query&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight sql"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;"accomodations"&lt;/span&gt; &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nv"&gt;"accomodations"&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;HAVING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rooms&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="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&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;"rooms"&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;"rooms"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"accomodation_id"&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;details&gt;&lt;summary class="pointer"&gt;Results&lt;/summary&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;#&amp;lt;Accomodation id: 4, name: "Home4", bathrooms_count: nil, guests_count: 7&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb282dd48 id: 7, accomodation_id: 4, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fb282dc08 id: 8, accomodation_id: 4, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 2, name: "Home2", bathrooms_count: nil, guests_count: 4&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb282e540 id: 3, accomodation_id: 2, name: "R1", beds_count: 4&amp;gt;
#&amp;lt;Room:0x00007f7fb282e338 id: 4, accomodation_id: 2, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 3, name: "Home3", bathrooms_count: nil, guests_count: 5&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb282e1d0 id: 5, accomodation_id: 3, name: "R1", beds_count: 6&amp;gt;
#&amp;lt;Room:0x00007f7fb282df28 id: 6, accomodation_id: 3, name: "R2", beds_count: 3&amp;gt;

#&amp;lt;Accomodation id: 1, name: "Home1", bathrooms_count: nil, guests_count: 3&amp;gt;
Rooms
#&amp;lt;Room:0x00007f7fb282e950 id: 1, accomodation_id: 1, name: "R1", beds_count: 2&amp;gt;
#&amp;lt;Room:0x00007f7fb282e680 id: 2, accomodation_id: 1, name: "R2", beds_count: 3&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;p&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;/p&gt;

&lt;h2&gt;Did you finish?&lt;/h2&gt;

&lt;p&gt;If you have already finished&amp;hellip; Awesome!! &lt;/p&gt;

&lt;p&gt;If you need more time&amp;hellip; It&amp;rsquo;s ok!&amp;hellip; Try to make some time in your schedule and try to finish it!&lt;/p&gt;

&lt;p&gt;If you find this post helpful, it would be nice if you can share it with someone that you think it also could be helpful =).&lt;/p&gt;

&lt;h2&gt;Do you want the code?&lt;/h2&gt;

&lt;p&gt;You can find this examples in two ways.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="/preload-includes-eager-load-examples-code.html"&gt;A repo with all the examples with the answers&lt;/a&gt; - You will be able to run the code, and experiment with all the exercises.&lt;/li&gt;
&lt;li&gt;&lt;a href="/preload-includes-eager-load-examples-code.html"&gt;A repo just the exercises, as a quiz&lt;/a&gt; - You will be able to test you knowledge and experiment a little more.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>How to attach an active storage file to an email</title>
    <link rel="alternate" href="https://bhserna.com/how-to-attach-an-active-storage-file-to-an-email"/>
    <id>https://bhserna.com/how-to-attach-an-active-storage-file-to-an-email</id>
    <published>2021-01-27T13:00:00Z</published>
    <updated>2021-01-27T13:00:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Are you using Active Storage to handle file uploading to the cloud (maybe AWS) and you want to know how to attach an uploaded file to an email?&amp;hellip; But you are not familiar with the Active Storage and Action Mailer APIs?&lt;/p&gt;

&lt;p&gt;I answered this question on reddit some days ago =)&amp;hellip; Here is how you can do it&amp;hellip;&lt;/p&gt;

&lt;p&gt;You need to &lt;code&gt;download&lt;/code&gt; the file and assign as and attachment, something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filename&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the context of the example&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&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_one_attached&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;photo&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;PhotoMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="ss"&gt;from: &lt;/span&gt;&lt;span class="s2"&gt;"default@example.com"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new_photo_email&lt;/span&gt;
    &lt;span class="n"&gt;photo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:product&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;photo&lt;/span&gt;
    &lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filename&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"one@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Nueva photo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhotoMailerPreview&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionMailer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Preview&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new_photo_email&lt;/span&gt;
    &lt;span class="no"&gt;PhotoMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;product: &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;first&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;new_photo_email&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;p&gt;Here the reference from the rails guides &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#downloading-files"&gt;https://edgeguides.rubyonrails.org/active&lt;em&gt;storage&lt;/em&gt;overview.html#downloading-files&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Why using &amp;ldquo;download&amp;rdquo; works?&lt;/h2&gt;

&lt;p&gt;If you are not familiar with Action Mailer and you are reading the docs, maybe is not that clear what you need to do. Here is what I understand.&lt;/p&gt;

&lt;p&gt;On the Action Mailer &lt;a href="https://guides.rubyonrails.org/action_mailer_basics.html#complete-list-of-action-mailer-methods"&gt;docs&lt;/a&gt; you can see&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;attachments['filename.jpg'] = File.read('/path/to/filename.jpg')

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

&lt;p&gt;Inside the &lt;code&gt;[]&lt;/code&gt; you need to put the name that you want for the attachment and you need to &lt;strong&gt;assign the content of the file&lt;/strong&gt;. Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;attachments[filename] = file_content

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

&lt;p&gt;In the example from the docs, &lt;code&gt;File.read&lt;/code&gt; returns the content of the file.&lt;/p&gt;

&lt;p&gt;Now, in the &lt;a href="https://edgeguides.rubyonrails.org/active_storage_overview.html#downloading-files"&gt;ActiveStorage docs&lt;/a&gt; says that you can &amp;ldquo;Use the attachment&amp;rsquo;s download method to read a blob’s binary data into memory&amp;rdquo;, and they use this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;binary = user.avatar.download

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

&lt;p&gt;That&amp;rsquo;s why I propose you to use &lt;code&gt;download&lt;/code&gt;. Apparently &lt;code&gt;download&lt;/code&gt; will return the content of the file wherever the file is stored.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>When is better to use preload or eager_load vs includes?</title>
    <link rel="alternate" href="https://bhserna.com/when-is-better-to-use-preload-or-eager-load-vs-includes"/>
    <id>https://bhserna.com/when-is-better-to-use-preload-or-eager-load-vs-includes</id>
    <published>2021-01-19T00:09:00Z</published>
    <updated>2021-01-19T00:09:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Even if you already know what is the difference between &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; and &lt;code&gt;eager_load&lt;/code&gt;, maybe is not that clear when is better for you to use one vs the other.&lt;/p&gt;

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

&lt;div class="pa3 ba b--light-blue bw2 bg-washed-blue"&gt;
If you don&amp;rsquo;t know the difference you can read&amp;hellip;
&lt;a href="https://bhserna.com/includes-preload-eager-load-joins-in-rails.html"&gt;
What is the difference between includes, preload, eager_load and joins?
&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;Well, as you may know, with Rails/ActiveRecord you can preload associations in 2 different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With a LEFT OUTER JOIN to pull all the associations in one query (&lt;code&gt;eager_load&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Making a separate query for each association (&lt;code&gt;preload&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Using &lt;code&gt;includes&lt;/code&gt; by default&lt;/h2&gt;

&lt;p&gt;When you use &lt;code&gt;includes&lt;/code&gt; Rails will decide which strategy to use for you. I think that this is a good default. Use &lt;code&gt;includes&lt;/code&gt; and let rails decide for you.&lt;/p&gt;

&lt;p&gt;But, to be honest, that is not what I do&amp;hellip;&lt;/p&gt;

&lt;h2&gt;Using &lt;code&gt;preload&lt;/code&gt; by default&lt;/h2&gt;

&lt;p&gt;I (at least for now) prefer to use &lt;code&gt;preload&lt;/code&gt; explicitly by default, because that is also the default for &lt;code&gt;includes&lt;/code&gt;, and I think that is easier to know
what is going to happen just by watching the code.&lt;/p&gt;

&lt;p&gt;I use &lt;code&gt;eager_load&lt;/code&gt; just when I already know that is significantly faster than the &lt;code&gt;preload&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;So, what should you do?&lt;/h2&gt;

&lt;p&gt;I would say, &amp;ldquo;Don&amp;rsquo;t worry to much&amp;rdquo;, and just pick one of this two strategies.&lt;/p&gt;

&lt;h2&gt;Do you want to practice?&lt;/h2&gt;

&lt;p&gt;Practice will help you understand this concepts better. I have prepared some
examples/excercises to help you understand how you can use this methods. Give it try!&lt;/p&gt;

&lt;p&gt;&lt;a href="/examples-to-learn-the-difference-between-preload-includes-eager-load.html"&gt;Examples to learn the difference between preload, includes or eager_load&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What is the difference between includes, preload, eager_load and joins in ActiveRecord?</title>
    <link rel="alternate" href="https://bhserna.com/includes-preload-eager-load-joins-in-rails"/>
    <id>https://bhserna.com/includes-preload-eager-load-joins-in-rails</id>
    <published>2021-01-07T22:22:00Z</published>
    <updated>2021-01-07T22:22:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Are you dealing with &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt;, &lt;code&gt;eager_load&lt;/code&gt; and &lt;code&gt;joins&lt;/code&gt;, maybe fixing some n+1 queries, but you are not really sure what&amp;rsquo;s the difference between all of them?&lt;/p&gt;

&lt;p&gt;Would you like to be able to say things like&amp;hellip; &amp;ldquo;Here I will use &lt;code&gt;preload&lt;/code&gt; instead of &lt;code&gt;includes&lt;/code&gt; because I have tested it and in this case an INNER JOIN is faster than a LEFT OUTER JOIN&amp;rdquo;&lt;/p&gt;

&lt;p&gt;Well, here is a little guide to understand what they do and what is the difference between them, to help you decide which is better for your case.&lt;/p&gt;

&lt;h2&gt;Example data&lt;/h2&gt;

&lt;p&gt;To explain the concepts imagine that you have the next data in your database&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;post&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Post 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Post 1 body"&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&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;body: &lt;/span&gt;&lt;span class="s2"&gt;"Comment 1"&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&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;body: &lt;/span&gt;&lt;span class="s2"&gt;"Comment 2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;post&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Post 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Post 2 body"&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&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;body: &lt;/span&gt;&lt;span class="s2"&gt;"Comment 1"&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&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;body: &lt;/span&gt;&lt;span class="s2"&gt;"Comment 2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Preload&lt;/h2&gt;

&lt;p&gt;It preloads the associociations using different queries.&lt;/p&gt;

&lt;p&gt;For example, this code will use one query for the posts and other for the comments.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts"&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2)  [[nil, 1], [nil, 2]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you will be able to use the comments without n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can include conditions for the base model.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&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;title: &lt;/span&gt;&lt;span class="s2"&gt;"Post 1"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts" WHERE "posts"."title" = $1  [["title", "Post 1"]]&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2)  [[nil, 1], [nil, 2]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But you can&amp;rsquo;t add conditions for the preloaded associations.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Hola"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# `prepare': PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "comments" (ActiveRecord::StatementInvalid)&lt;/span&gt;
&lt;span class="c1"&gt;# LINE 1: SELECT "posts".* FROM "posts" WHERE "comments"."body" = $1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Eager load&lt;/h2&gt;

&lt;p&gt;It forces the eager loading performing a LEFT OUTER JOIN.&lt;/p&gt;

&lt;p&gt;For example, this code will use just one query to load both, the posts and the comments.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "comments"."id" AS t1_r0, "comments"."body" AS t1_r1, "comments"."post_id" AS t1_r2 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you will be able to use the comments without n+1 queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will be able to add conditions for the base model.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;title: &lt;/span&gt;&lt;span class="s2"&gt;"Hola"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "comments"."id" AS t1_r0, "comments"."body" AS t1_r1, "comments"."post_id" AS t1_r2 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE "posts"."title" = $1  [["title", "Hola"]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you also will be able to add conditions for the associated models.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Hola"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "comments"."id" AS t1_r0, "comments"."body" AS t1_r1, "comments"."post_id" AS t1_r2 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE "comments"."body" = $1  [["body", "Hola"]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Includes&lt;/h2&gt;

&lt;p&gt;By default it will load the included associations using different queries. Like preload.&lt;/p&gt;

&lt;p&gt;For example, this code will use one query for the posts and other for the comments.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&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;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts"&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2)  [[nil, 1], [nil, 2]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And you will be able to use the comments without new queries.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;posts&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;:comments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will also be able to include add conditions for the base model.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# Post.includes(:comments).where(title: "Hola").to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts" WHERE "posts"."title" = $1  [["title", "Post 1"]]&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2)  [[nil, 1], [nil, 2]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in this case, you will also be able to add conditions for the included associations. Because it will perform a LEFT OUTER JOIN like eager load does.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;comments: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="s2"&gt;"Hola"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "comments"."id" AS t1_r0, "comments"."body" AS t1_r1, "comments"."post_id" AS t1_r2 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE "comments"."body" = $1  [["body", "Hola"]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note that if you want to pass the coditions as strings, you will need to use the &lt;code&gt;references&lt;/code&gt; method, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;"comments.body = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Hola"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, &lt;code&gt;includes&lt;/code&gt; is like a mix between &lt;code&gt;preload&lt;/code&gt; and &lt;code&gt;eager_load&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Joins&lt;/h2&gt;

&lt;p&gt;By default it will perform an INNER JOIN that you could use to create a more specific query using the joined tables.&lt;/p&gt;

&lt;p&gt;For example you can ask for the comments for a post with an specific title, like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&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;post: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Post 1"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" INNER JOIN "posts" post ON post."id" = "comments"."post_id" WHERE "post"."title" = $1  [["title", "Post 1"]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But it will not preload the &lt;code&gt;post&lt;/code&gt; for each &lt;code&gt;comment&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="c1"&gt;# comments.map(&amp;amp;:post)&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to preload the post, you would need to use one of the methods above (includes, preload, eager load)&amp;hellip;&lt;/p&gt;

&lt;p&gt;For example&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post&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;post: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Post 1"&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;:post&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "comments".* FROM "comments" INNER JOIN "posts" post ON post."id" = "comments"."post_id" WHERE "post"."title" = $1  [["title", "Post 1"]]&lt;/span&gt;
&lt;span class="c1"&gt;# SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1  [["id", 2]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And now you will avoid the n+1 queries when you do&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;comments&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;:post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Do you want more examples?&lt;/h2&gt;

&lt;p&gt;Maybe more examples will help you understand this concepts better. I have prepared some
examples to help you understand how you can use this 4 methods. Give it look!&lt;/p&gt;

&lt;p&gt;&lt;a href="/examples-to-learn-the-difference-between-preload-includes-eager-load.html"&gt;Examples to learn the difference between preload, includes or eager_load&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Do you want to practice?&lt;/h2&gt;

&lt;p&gt;Practice will help you understand this concepts better. I have prepared some
examples/excercises to help you understand how you can use this 4 methods. Give it try!&lt;/p&gt;

&lt;p&gt;&lt;a href="/quiz-to-practice-the-difference-between-preload-includes-or-eager_load.html"&gt;Quiz to practice the difference between preload, includes or eager_load&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Useful links&lt;/h2&gt;

&lt;p&gt;You can use the rails docs to learn more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-preload"&gt;preload&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-eager_load"&gt;eager_load&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes"&gt;includes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-joins"&gt;joins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>5 ways to fetch the latest-N-of-each record on Rails</title>
    <link rel="alternate" href="https://bhserna.com/5-ways-to-fetch-the-latest-n-of-each-record-on-rails"/>
    <id>https://bhserna.com/5-ways-to-fetch-the-latest-n-of-each-record-on-rails</id>
    <published>2020-12-23T22:22:00Z</published>
    <updated>2020-12-23T22:22:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;A common cause of n+1 queries is fetching the &amp;ldquo;latest-N-of-each&amp;rdquo; record on a
list of records.&lt;/p&gt;

&lt;p&gt;Some examples of this problem are trying to get&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The latest comments for each post&lt;/li&gt;
&lt;li&gt;The latest payments for each customer&lt;/li&gt;
&lt;li&gt;The latest review for each product&lt;/li&gt;
&lt;li&gt;The better prices for each product&lt;/li&gt;
&lt;li&gt;Etc&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is that once you have detected the n+1 queries, is not that easy to
avoid them. Here I will show you 5 ways to solve this problem avoiding the n+1
queries.&lt;/p&gt;

&lt;p&gt;But first, let&amp;rsquo;s explain the problem with an example&amp;hellip;&lt;/p&gt;

&lt;h2 id="example"&gt;Example problem&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fetch the latest 3 comments for each post&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine, that you have a model like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comments&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;id: :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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How do you render the latest comments for each post, without n+1 queries?&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;p&gt;&amp;hellip; you can&amp;rsquo;t just &lt;code&gt;includes(:comments)&lt;/code&gt; and call &lt;code&gt;latest_comments&lt;/code&gt;, because &lt;a href="/why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record.html"&gt;active
record will &amp;ldquo;ignore your includes&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Well, here are 5 ways to do it&amp;hellip;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;
&lt;strong&gt;Note:&lt;/strong&gt;
If you want to fetch just the &amp;ldquo;latest-for-each&amp;rdquo;. Maybe you want to take a look
to this post:
&lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;
  5 ways to fix the latest-comment n+1 problem
&lt;/a&gt;
&lt;/em&gt;&lt;/p&gt;

&lt;h2 id="code"&gt;Do you want to run the examples?&lt;/h2&gt;

&lt;p&gt;You can get the code on:
&lt;a href="tool-to-run-the-latest-n-of-each-record-examples.html"&gt;Tool to run the examples for the 5 ways to fetch the latest-n-of-each record&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use it to run the examples and play with the code and seed values, to pick the right solution for you current case.&lt;/p&gt;

&lt;h2 id="fetch_all"&gt;1. An object to fetch all and select just the latest N&lt;/h2&gt;

&lt;p&gt;You can create a new object to fetch all the comments, group them by &lt;code&gt;post&lt;/code&gt; and
then return just the latest 3 comments for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&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;LatestCommentsForPosts&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_for&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="nf"&gt;group_by&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;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;[&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;id&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;first&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="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scope_for&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="no"&gt;Comment&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;id: :desc&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&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="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LatestCommentsForPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&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;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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h2 id="fetch_just_latest_n"&gt;2. An object to fetch just the latest N for each record &lt;/h2&gt;

&lt;h3 id="fetch_just_latest_n_window_functions"&gt;Using window functions&lt;/h3&gt;

&lt;p&gt;If fetching all the comments for each post is causing you memory or performance
problems, you can use &lt;a href="https://www.postgresql.org/docs/current/tutorial-window.html"&gt;&amp;ldquo;Window
functions&amp;rdquo;&lt;/a&gt; to
ask the database for just the latest 3 comments for each post, group them by
post and then return the comments for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&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;LatestCommentsForPosts&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_for&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="nf"&gt;group_by&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;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scope_for&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;ranked_comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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;post_id: &lt;/span&gt;&lt;span class="n"&gt;posts&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="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      comments.*,
      dense_rank() OVER (
        PARTITION BY comments.post_id
        ORDER BY comments.id DESC
      ) AS comment_rank
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;"comment_rank &amp;lt;= 3"&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="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LatestCommentsForPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&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;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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h3 id="fetch_just_latest_n_lateral_join"&gt;Using lateral join&lt;/h3&gt;

&lt;p&gt;Other approach that you could try is using a &lt;a href="https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-LATERAL"&gt;&amp;ldquo;Lateral join&amp;rdquo;&lt;/a&gt;
to fetch just the latest 3 comments for each post, group them by
post and then return the comments for each post.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&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;LatestCommentsForPosts&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope_for&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="nf"&gt;group_by&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;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scope_for&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;latest_comments&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest_comments.*"&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;id: &lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM comments
          WHERE post_id = posts.id
          ORDER BY id DESC LIMIT 3
        ) AS latest_comments ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

    &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;id: :desc&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="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;LatestCommentsForPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;posts&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="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="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_post&lt;/span&gt;&lt;span class="p"&gt;(&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;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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h2 id="association"&gt;3. An association to fetch just the latest N for each record&lt;/h2&gt;

&lt;p&gt;You can also represent the latest comments as an association&amp;hellip;&lt;/p&gt;

&lt;h3 id="association_window_functions"&gt;Using window functions&lt;/h3&gt;

&lt;p&gt;You can use &amp;ldquo;&lt;a href="https://www.postgresql.org/docs/current/tutorial-window.html"&gt;&amp;quot;Window
functions&amp;rdquo;&lt;/a&gt; to
build a scope for just the latest 3 comments for each post, and pass that scope
to the association&amp;rsquo;s definition.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
    &lt;span class="n"&gt;ranked_comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&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="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      comments.*,
      dense_rank() OVER (
        PARTITION BY comments.post_id
        ORDER BY comments.id DESC
      ) AS comment_rank
&lt;/span&gt;&lt;span class="no"&gt;    SQL&lt;/span&gt;

    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;"comment_rank &amp;lt;= 3"&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="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:latest_comments&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="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h3 id="association_lateral_join"&gt;Using lateral join&lt;/h3&gt;

&lt;p&gt;You can use a &lt;a href="https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-LATERAL"&gt;&amp;ldquo;Lateral join&amp;rdquo;&lt;/a&gt;
to build an scope for just the latest 3 comments for each post, and pass that scope
to the association&amp;rsquo;s definition.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
    &lt;span class="n"&gt;latest_comments&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest_comments.*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
        JOIN LATERAL (
          SELECT * FROM comments
          WHERE post_id = posts.id
          ORDER BY id DESC LIMIT 3
        ) AS latest_comments ON TRUE
&lt;/span&gt;&lt;span class="no"&gt;      SQL&lt;/span&gt;

    &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"comments"&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;id: :desc&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="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:latest_comments&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="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h2 id="russian_doll"&gt;4. Russian doll caching&lt;/h2&gt;

&lt;p&gt;If you are already using fragment cache on your app, you can embrace the
&amp;ldquo;Russian doll caching&amp;rdquo; and &lt;a href="https://www.youtube.com/watch?v=ktZLpjCanvg"&gt;think of n+1 as a
feature&lt;/a&gt;, let the n+1 queries
run just the first time and use the cache after that.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comments&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;id: :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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In your view…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="c1"&gt;#posts/index.html.erb &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;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'posts/post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cached: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="c1"&gt;#posts/_post.html.erb &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;cache&lt;/span&gt; &lt;span class="n"&gt;post&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;latest_comments&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;comment&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;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;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;h2 id="record_latest_n"&gt;5. A new record to cache the latest N for each record&lt;/h2&gt;

&lt;p&gt;If the other solutions are not enough for you, you can try to add a new record to keep a cache of the
latest comments for each post using a &lt;code&gt;has_many :through&lt;/code&gt; association and keep the latest comments in sync
&amp;ldquo;manually&amp;rdquo; (maybe with an &lt;code&gt;after_commit&lt;/code&gt; callback).&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment_caches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"LatestCommentCache"&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;through: :latest_comment_caches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;source: :comment&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_latest_comment_caches&lt;/span&gt;
    &lt;span class="n"&gt;latest_comment_caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete_all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_latest_comments&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;id: :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;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;LatestCommentCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&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;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LatestCommentCache&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_for_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&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;delete_latest_comment_caches&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;calculate_latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;post: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comment: &lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&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;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:latest_comments&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="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments&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;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h2 id="pick_one"&gt;Now you can pick one&lt;/h2&gt;

&lt;p&gt;Now you know 5 difference ways to solve the problem, and you can pick the
one that better fits your needs =)&lt;/p&gt;

&lt;p&gt;If you are not sure, I think you can use the first one, &lt;a href="#fetch_all"&gt;an object to fetch all and select just the latest N&lt;/h2&gt;
default order&lt;/a&gt;, unless your metrics are telling you that you need something
different.&lt;/p&gt;

&lt;h2&gt;More about window functions&lt;/h2&gt;

&lt;p&gt;Here are some useful articles if you want to learn more about window functions
and how to use them in rails.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/tutorial-window.html"&gt;Window functions&lt;/a&gt;
- If you want to learn how they work in postgres, this is a link to the docs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://thoughtbot.com/blog/active-record-eager-loading-with-query-objects-and-decorators"&gt;ActiveRecord Eager Loading with Query Objects and Decorators&lt;/a&gt;
- A post from Thoughtbot with a proposal to use query objects, decorators and window functions to model this problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/vovimayhem/rails-last-n-of-each-demo"&gt;Demo: &amp;ldquo;Last N of each&amp;rdquo; query with PostgreSQL and Rails ActiveRecord&lt;/a&gt;
- A demo from &lt;a href="https://github.com/vovimayhem"&gt;vovimayhem&lt;/a&gt; to help you learn how to translate the SQL for the window functions to Arel and Rails.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;More about lateral join&lt;/h2&gt;

&lt;p&gt;Here are some useful articles if you want to learn more about the lateral join in postgres.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-LATERAL"&gt;Lateral queries&lt;/a&gt;
- If you want to learn how they work in postgres, this is a link to the docs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://charlesnagy.info/it/postgresql/group-by-limit-per-group-in-postgresql"&gt;Group by limit per group in PostgreSQL&lt;/a&gt;
- A post that help me understand how you can use the lateral join in a &amp;ldquo;top-n-per-group&amp;rdquo; query&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/codeforamerica/open311status/pull/98"&gt;Use lateral join when finding latest City status&lt;/a&gt;
- A pull request with a refactor from window functions to lateral join&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>10 Examples to learn when your ActiveRecord query will be executed</title>
    <link rel="alternate" href="https://bhserna.com/10-examples-to-learn-when-your-activerecord-query-will-be-executed"/>
    <id>https://bhserna.com/10-examples-to-learn-when-your-activerecord-query-will-be-executed</id>
    <published>2020-12-08T23:29:00Z</published>
    <updated>2020-12-08T23:29:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you struggle fixing N+1 queries because is hard for you to detect why ActiveRecord seems to ignore your &amp;ldquo;includes&amp;rdquo;?&lt;/p&gt;

&lt;p&gt;Wouldn&amp;rsquo;t it be nice to just see some piece of code and know when the query will execute?&lt;/p&gt;

&lt;p&gt;Well… this exercise will try to help you achieve this… to help you identify when a query will execute by just watching the code :)&lt;/p&gt;

&lt;h2&gt;Instructions&lt;/h2&gt;

&lt;p&gt;I will show you 10 code examples using ActiveRecord&amp;rsquo;s query interface, like &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt; and &lt;code&gt;where&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For each code example you will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Try to identify when ActiveRecord will execute de query&lt;/li&gt;
&lt;li&gt;Compare your answer with the one I will provide after the example.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="pa3 ba b--light-yellow bw2 bg-washed-yellow"&gt;
&lt;strong&gt;Warning!&lt;/strong&gt;
&lt;p class="mt1"&gt;
If you are just starting, the excercises could be hard!&amp;hellip; Don&amp;rsquo;t worry, that&amp;rsquo;s
expected, with practice it will be easier!
&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;Excercise 1&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 2&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 3&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&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;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.all.each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt; on &lt;code&gt;post.sorted_comments&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 4&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.includes(:comments).each&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 5&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.includes(:comments).each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt; on &lt;code&gt;post.comments.order(:id).to_a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 6&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.includes(:sorted_comments).each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt; on &lt;code&gt;post.comments.to_a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 7&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.includes(:sorted_comments).each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;first&lt;/code&gt; on &lt;code&gt;post.comments.first&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 8&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&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;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;map&lt;/code&gt; on &lt;code&gt;Post.includes(:sorted_comments).map&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;last&lt;/code&gt; on &lt;code&gt;post.comments.last&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 9&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort_by&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;:id&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted_comments&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;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;sort_by&lt;/code&gt; on &lt;code&gt;Post.includes(:sorted_comments).sort_by(&amp;amp;:id).each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;first&lt;/code&gt; on &lt;code&gt;post.sorted_comments.limit(1).first&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;first&lt;/code&gt; on &lt;code&gt;post.comments.to_a.limit(1)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Excercise 10&lt;/h2&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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;"comments.points &amp;gt; 5"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_a&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;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;details&gt;
&lt;summary class="pointer"&gt;Answer&lt;/summary&gt;
&lt;ul&gt;
  &lt;li&gt;When calling &lt;code&gt;each&lt;/code&gt; on &lt;code&gt;Post.includes(:comments).where(&amp;quot;comments.points &amp;gt; 5&amp;quot;).references(:comments).each&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;When calling &lt;code&gt;to_a&lt;/code&gt; on &lt;code&gt;post.comments.order(:id).to_a&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;&lt;/p&gt;

&lt;h2&gt;Practice more&amp;hellip;&lt;/h2&gt;

&lt;p&gt;Been able to identify when a query will execute, just by watching the code is
an ability that will make you job, much more easy.&lt;/p&gt;

&lt;p&gt;Maybe at the beginning it will be hard, but the more you practice the easier
that it will be.&lt;/p&gt;

&lt;p&gt;So if you already can answer all the excercises right, try to create your
own excercises and practice until you feel confident of your ability.&lt;/p&gt;

&lt;p&gt;Other thing that you could do is to actually run the code of each excercise and
also experiment changing them to try to understand what&amp;rsquo;s happening. You can
find the code on
&lt;a href="https://github.com/bhserna/when_the_query_is_executed"&gt;bhserna/when the query is executed&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Why Active Record seems to ignore your "includes" and runs a query for each record?</title>
    <link rel="alternate" href="https://bhserna.com/why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record"/>
    <id>https://bhserna.com/why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record</id>
    <published>2020-11-20T13:26:00Z</published>
    <updated>2020-11-20T13:26:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you have a method that works fine with a single record, but when you use it on a list causes N+1 queries?&lt;/p&gt;

&lt;p&gt;Imagine that you have a model like this…&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&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&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&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;p&gt;When you are fetching a single post there is no problem…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when you try to fetch a list of posts, the method seems to ignore the &lt;code&gt;includes&lt;/code&gt;, and runs a query for each post to get the &lt;code&gt;latest_comment&lt;/code&gt; for each post!&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;I think that the first step, to fix this kind of problems, is to understand how the method chaining in Active Record works.&lt;/p&gt;

&lt;h2&gt;Method chaining in Active Record&lt;/h2&gt;

&lt;p&gt;Active Record implements &amp;ldquo;method chaining&amp;rdquo; which allow us tu use multiple Active Record methods together.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;can chain&lt;/strong&gt; methods in a statement when the previous method called returns an &lt;code&gt;ActiveRecord::Relation&lt;/code&gt;, like &lt;code&gt;all&lt;/code&gt;, &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;joins&lt;/code&gt; and &lt;code&gt;order&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;can&amp;rsquo;t chain&lt;/strong&gt; Active Record methods, after a call to a method that &lt;strong&gt;does not&lt;/strong&gt; return an &lt;code&gt;ActiveRecord::Relation&lt;/code&gt;, like &lt;code&gt;to_a&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt; or &lt;code&gt;last&lt;/code&gt;. You need to put those methods at the end of the statement.&lt;/p&gt;

&lt;div class="pa3 ba b--light-blue bw2 bg-washed-blue"&gt;
Note: You can learn more on the &lt;a href="https://guides.rubyonrails.org/active_record_querying.html#understanding-method-chaining" target="_blank"&gt;rails guides&lt;/a&gt;
&lt;/div&gt;

&lt;h2&gt;What is the problem in the example?&lt;/h2&gt;

&lt;p&gt;When you are fetching a single post there is no problem, because all the methods before the call to &lt;code&gt;last&lt;/code&gt; return an &lt;code&gt;ActiveRecord::Relation&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you try each link of the chain, you will see that just the call to &lt;code&gt;last&lt;/code&gt; does not return an ActiveRecord::Relation.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="o"&gt;=&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;Relation&lt;/span&gt;
&lt;span class="n"&gt;comments&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;comments&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; true&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; true&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;#=&amp;gt; false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when you try to fetch the list…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At the moment you call &lt;code&gt;each&lt;/code&gt; on the &lt;code&gt;ActiveRecord::Relation&lt;/code&gt;, it will execute the query, and in your logs you will see something like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Post Load (0.2ms)  SELECT "posts".* FROM "posts"
Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ORDER BY "comments"."id" ASC  [["post_id", 2001], ["post_id", 2002], ["post_id", 2003], ["post_id", 2004], ["post_id", 2005], ["post_id", 2006], ["post_id", 2007], ["post_id", 2008], ["post_id", 2009], ["post_id", 2010]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But then for each post it will execute a new query, because although you already have the comments loaded, with &lt;code&gt;order(:created_at).last&lt;/code&gt; you are building a new query to fetch
the latest comment with a different order.&lt;/p&gt;

&lt;p&gt;That is the reason why when you try to fetch a list of posts, the method seems to ignore the includes, and runs a query for each post to get the &lt;code&gt;latest_comment&lt;/code&gt; for each post.&lt;/p&gt;

&lt;h2&gt;How can you solve this problem?&lt;/h2&gt;

&lt;p&gt;Maybe your problem is not exactly like the example, but if you are also trying to fetch the latest &amp;ldquo;X&amp;rdquo; in a list of records I think that you can use an &lt;a href="https://bhserna.com/association-with-default-order-to-fix-the-latest-comment-n-1-problem.html"&gt;association with a default order&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Something like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It won&amp;rsquo;t execute n+1 queries, because you are fetching the comments already on the right order, and you don&amp;rsquo;t need to ask the database to sort them again.&lt;/p&gt;

&lt;p&gt;If you want to see other options you can see &lt;a href="https://bhserna.com/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;this post with some other ways to solve the problem&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Do you want to practice?&lt;/h2&gt;

&lt;p&gt;Now that you understand when the query will be execute, maybe you will want to do a little excercise to practice this ability and try to make it automatic.&lt;/p&gt;

&lt;p&gt;You can try this little excercise with &lt;a href="/10-examples-to-learn-when-your-activerecord-query-will-be-executed.html"&gt;10 examples to help you learn when your ActiveRecord query will be executed&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PDF with the benchmarks for the fixes to the latest-comment n+1 problem</title>
    <link rel="alternate" href="https://bhserna.com/pdf-with-the-benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem"/>
    <id>https://bhserna.com/pdf-with-the-benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem</id>
    <published>2020-11-19T22:19:00Z</published>
    <updated>2020-11-19T22:19:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;When you are trying to solve an n+1 problem, hopefully you will find different ways to solve it. But what is the one that you should use?&lt;/p&gt;

&lt;p&gt;Talking specifically about the &lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;fixes that I share to you to solve the &amp;ldquo;latest
comment&amp;rdquo; n+1 queries
problem&lt;/a&gt;… I think you can
use the first one, &lt;a href="/association-with-default-order-to-fix-the-latest-comment-n-1-problem.html"&gt;&amp;ldquo;the association with default
order&amp;rdquo;&lt;/a&gt;,
unless your metrics are telling you that you need something different.&lt;/p&gt;

&lt;p&gt;But any way, I think that &lt;strong&gt;been aware of the performance implications of your
code and understanding the tradeoffs is a good thing&lt;/strong&gt;…&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s why I want to share with you some benchmarks to the 5 fixes in different
scenarios, to help you understand which solution is better in each case, and
which solution would be enough for your case.&lt;/p&gt;

&lt;p&gt;I have already shared the &lt;a href="benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;benchmarks for this 5
fixes&lt;/a&gt; on html
format.&lt;/p&gt;

&lt;p&gt;But now I put the report also on PDF =)&lt;/p&gt;

&lt;p&gt;&lt;script async data-uid="99fcf4a14f" src="https://bhserna.ck.page/99fcf4a14f/index.js"&gt;&lt;/script&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tool to run the benchmark for fixes for the latest-comment n+1 queries problem</title>
    <link rel="alternate" href="https://bhserna.com/tool-to-run-the-benchmark-for-fixes-for-the-latest-comment-n-1-queries-problem"/>
    <id>https://bhserna.com/tool-to-run-the-benchmark-for-fixes-for-the-latest-comment-n-1-queries-problem</id>
    <published>2020-11-14T12:57:00Z</published>
    <updated>2020-11-14T12:57:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;What fix should you pick for your &amp;ldquo;latest-comment&amp;rdquo; n+1 queries problem?&lt;/p&gt;

&lt;p&gt;One example of this problem is trying to get the latest comment on a list of
posts, but there are others, like the last review in a list of products, or the
cheapest price, or the latest payment for each costumer, etc…&lt;/p&gt;

&lt;p&gt;I have already shared &lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;some fixes to help you solve the problem&lt;/a&gt;.
and some &lt;a href="/benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;benchmarks for this fixes&amp;hellip;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the code that I use to run those benchmarks. You can use it to play with the code and seed values, to make it more specific to your current problem.&lt;/p&gt;

&lt;h2&gt;Benchmark structure&lt;/h2&gt;

&lt;p&gt;The benchmark will include the memory and the &amp;ldquo;iterations per second&amp;rdquo; benchmarks.&lt;/p&gt;

&lt;p&gt;With this &amp;ldquo;reports&amp;quot;…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sorted_comments&lt;/code&gt; - Default order for the &amp;quot;has many&amp;rdquo; association&lt;/li&gt;
&lt;li&gt;&lt;code&gt;latest_comment&lt;/code&gt; - A &amp;ldquo;has one&amp;rdquo; association for the latest comment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feed looping&lt;/code&gt; - Looping through the latest comment for each post&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cached feed&lt;/code&gt; - A version of the looping where we cache feed in memory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;russian_doll&lt;/code&gt; - Russian doll-caching (with a simpler cache mechanism)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cached_comment&lt;/code&gt; - Caching the latest comment in a column&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;The link&lt;/h2&gt;

&lt;p&gt;You can see the code on github at &lt;a href="https://github.com/bhserna/latest_comment_n-1_fixes_benchmark"&gt;bhserna/latest_comment_n-1_fixes_benchmark&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cheatsheet to help you decide which fix for the last-comment n+1 problem is the best for you</title>
    <link rel="alternate" href="https://bhserna.com/cheatsheet-to-help-you-decide-which-fix-for-the-last-comment-n-1-problem-is-the-best-for-you"/>
    <id>https://bhserna.com/cheatsheet-to-help-you-decide-which-fix-for-the-last-comment-n-1-problem-is-the-best-for-you</id>
    <published>2020-11-13T02:38:00Z</published>
    <updated>2020-11-13T02:38:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you have to fetch the latest &amp;ldquo;x&amp;rdquo; of each record? Not sure how to avoid
the n+1 queries?&lt;/p&gt;

&lt;p&gt;Hopefully you will find different ways to solve it. But what is the one that
you should use?&lt;/p&gt;

&lt;p&gt;You can search the internet, but opinions can be overwhelming and confusing,
and most of the time not specific to your specific usecase.&lt;/p&gt;

&lt;p&gt;I have already shared &lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;some fixes that to solve the &amp;ldquo;latest comment&amp;rdquo; n+1
queries problem&lt;/a&gt; and
some &lt;a href="benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;benchmarks for this 5
fixes&lt;/a&gt;&amp;hellip;&lt;/p&gt;

&lt;p&gt;Now I want to share with you a little cheatsheet to help you decide which fix
to pick in different contexts, based mostly on my interpretation of the
benchmarks and the complexity of each solution.&lt;/p&gt;

&lt;p&gt;I hope it can help you =)&lt;/p&gt;

&lt;p&gt;&lt;script async data-uid="c2d5765cd3" src="https://bhserna.ck.page/c2d5765cd3/index.js"&gt;&lt;/script&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Tool to run the fixes for the latest-comment n+1 queries problem</title>
    <link rel="alternate" href="https://bhserna.com/tool-to-run-the-fixes-for-the-latest-comment-n-1-queries-problem"/>
    <id>https://bhserna.com/tool-to-run-the-fixes-for-the-latest-comment-n-1-queries-problem</id>
    <published>2020-11-10T12:56:00Z</published>
    <updated>2020-11-10T12:56:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you have to fetch the latest &amp;ldquo;x&amp;rdquo; of each record?&lt;/p&gt;

&lt;p&gt;One example of this problem is trying to get the latest comment on a list of
posts, but there are others, like the last review in a list of products, or the
cheapest price, or the latest payment for each costumer, etc…&lt;/p&gt;

&lt;p&gt;I want to share with you the code I used in the post &lt;a href="https://bhserna.com/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;5 ways to fix the latest-comment n+1
problem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use it to run the examples and play with the code and seed values,
to pick the right solution for you current case.&lt;/p&gt;

&lt;p&gt;You can see the code on github at &lt;a href="https://github.com/bhserna/latest_comment_n-1_examples"&gt;bhserna/latest_comment_n-1_examples&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Benchmarks for the fixes to the latest-comment n+1 problem</title>
    <link rel="alternate" href="https://bhserna.com/benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem"/>
    <id>https://bhserna.com/benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem</id>
    <published>2020-11-02T14:19:00Z</published>
    <updated>2020-11-02T14:19:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;What fix should you pick for your n+1 queries problem?&lt;/p&gt;

&lt;p&gt;When you are trying to solve an n+1 problem, hopefully you will find different ways to solve it. But what is the one that you should use?&lt;/p&gt;

&lt;p&gt;You can search the internet, but opinions can be overwhelming and confusing, because…
&amp;ldquo;Window functions are great option here&amp;quot;… or &amp;quot;Window functions will work, but they can be horrible on performance&amp;quot;… &amp;quot;I&amp;rsquo;d go with caching (in a column)&amp;quot;… &amp;quot;You should use Russian-doll caching&amp;quot;… &amp;quot;I&amp;rsquo;d write custom SQL&amp;quot;…&lt;/p&gt;

&lt;p&gt;Talking specifically about the &lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;fixes that I share to you to solve the &amp;quot;latest comment&amp;rdquo; n+1 queries problem&lt;/a&gt;… I think you can use the first one, &lt;a href="/association-with-default-order-to-fix-the-latest-comment-n-1-problem.html"&gt;&amp;ldquo;the association with default order&amp;rdquo;&lt;/a&gt;, unless your metrics are telling you that you need something different.&lt;/p&gt;

&lt;p&gt;But any way, I think that &lt;strong&gt;been aware of the performance implications of your code and understanding the tradeoffs is a good thing&lt;/strong&gt;…&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s why I want to share with you some benchmarks to the 5 fixes in different
scenarios, to help you understand which solution is better in each case, and
which solution would be enough for your case.&lt;/p&gt;

&lt;div class="pa3 ba b--light-blue bw2 bg-washed-blue"&gt;
  &lt;strong&gt;Want a pdf with the report?&lt;/strong&gt;
  &lt;p class="ma0"&gt;
  Get it here:
  &lt;br/&gt;
  &lt;a href="/pdf-with-the-benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html" target="_blank"&gt;
  Benchmarks for the fixes to the latest-comment n+1 problem
  &lt;/a&gt;
  &lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;Benchmark structure&lt;/h2&gt;

&lt;p&gt;You will have results for this scenarios&amp;hellip;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Posts: 20, Comments: 5 per post&lt;/li&gt;
&lt;li&gt;Posts: 20, Comments: 10 per post&lt;/li&gt;
&lt;li&gt;Posts: 20, Comments: 50 per post&lt;/li&gt;
&lt;li&gt;Posts: 20, Comments: 100 per post&lt;/li&gt;
&lt;li&gt;Posts: 20, Comments: 1000 per post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scenario will include the memory and the &amp;ldquo;iterations per second&amp;rdquo; benchmarks.&lt;/p&gt;

&lt;p&gt;With this &amp;ldquo;reports&amp;quot;…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sorted_comments&lt;/code&gt; - Default order for the &amp;quot;has many&amp;rdquo; association&lt;/li&gt;
&lt;li&gt;&lt;code&gt;latest_comment&lt;/code&gt; - A &amp;ldquo;has one&amp;rdquo; association for the latest comment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feed looping&lt;/code&gt; - Looping through the latest comment for each post&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cached feed&lt;/code&gt; - A version of the looping where we cache feed in memory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;russian_doll&lt;/code&gt; - Russian doll-caching (with a simpler cache mechanism)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cached_comment&lt;/code&gt; - Caching the latest comment in a column&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the reports where tested in a MacBook Pro (13-inch 2020) with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processor: 1.4 Ghz Intel Core i5, 4 cores&lt;/li&gt;
&lt;li&gt;Memory: 8gb&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Benchmark code&lt;/h2&gt;

&lt;p&gt;This is the code used for the benchmark&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./config"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"./seeds"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark/memory"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"benchmark/ips"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;cached_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&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;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_cached_comment&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_cached_comment&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cached_comment_id: &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Feed&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;posts&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;fetch_posts&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_posts&lt;/span&gt;
    &lt;span class="n"&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;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_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="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;FeedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeedPost&lt;/span&gt;
  &lt;span class="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:latest_comment&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;
    &lt;span class="vi"&gt;@latest_comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latest_comment&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@cache&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="vi"&gt;@cache&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clear_cache!&lt;/span&gt;
  &lt;span class="vi"&gt;@cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Seeds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;posts_count: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comments_count: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Memory Benchmark"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"---------"&lt;/span&gt;

&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&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;benchmark&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sorted_comments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&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;:sorted_comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest_comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&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;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"feed looping"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Feed&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="nf"&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cached feed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cached_comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&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;:cached_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"russian doll"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="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="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;clear_cache!&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Benchmark ips"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"---------"&lt;/span&gt;


&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ips&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:warmup&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sorted_comments"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;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;:sorted_comments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"latest_comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;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;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"feed looping"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Feed&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="nf"&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cached feed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cached_comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;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;:cached_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"russian doll"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;p&gt;You can see the code on github at
&lt;a href="https://github.com/bhserna/latest_comment_n-1_fixes_benchmark"&gt;bhserna/latest_comment_n-1_fixes_benchmark&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Benchmark results&lt;/h2&gt;

&lt;p&gt;Now here you have the reports…&lt;/p&gt;

&lt;h3&gt;Scenario 1: Posts: 20, Comments: 5 per post&lt;/h3&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Memory Benchmark
---------
Calculating -------------------------------------
     sorted_comments   484.348k memsize (    15.950k retained)
                         5.630k objects (   146.000  retained)
                        50.000  strings (    50.000  retained)
      latest_comment   116.855k memsize (     2.065k retained)
                         1.426k objects (    16.000  retained)
                        50.000  strings (    15.000  retained)
        feed looping    55.176k memsize (     0.000  retained)
                       702.000  objects (     0.000  retained)
                        50.000  strings (     0.000  retained)
         cached feed    55.136k memsize (    35.840k retained)
                       701.000  objects (   390.000  retained)
                        50.000  strings (    45.000  retained)
      cached_comment    70.032k memsize (    40.000  retained)
                       883.000  objects (     1.000  retained)
                        50.000  strings (     1.000  retained)
        russian doll   488.744k memsize (     1.304k retained)
                         7.808k objects (    24.000  retained)
                        50.000  strings (    20.000  retained)

Comparison:
         cached feed:      55136 allocated
        feed looping:      55176 allocated - 1.00x more
      cached_comment:      70032 allocated - 1.27x more
      latest_comment:     116855 allocated - 2.12x more
     sorted_comments:     484348 allocated - 8.78x more
        russian doll:     488744 allocated - 8.86x more

Benchmark ips
---------
Warming up --------------------------------------
     sorted_comments    29.000  i/100ms
      latest_comment    58.000  i/100ms
        feed looping    92.000  i/100ms
         cached feed    14.814k i/100ms
      cached_comment    76.000  i/100ms
        russian doll   324.000  i/100ms
Calculating -------------------------------------
     sorted_comments    317.872  (± 4.4%) i/s -      1.595k in   5.026951s
      latest_comment    606.529  (± 4.1%) i/s -      3.074k in   5.077027s
        feed looping      1.020k (± 3.4%) i/s -      5.152k in   5.056148s
         cached feed    153.459k (± 2.5%) i/s -    770.328k in   5.022960s
      cached_comment    803.337  (± 2.7%) i/s -      4.028k in   5.017891s
        russian doll      3.322k (± 2.1%) i/s -     16.848k in   5.073899s

Comparison:
         cached feed:   153459.3 i/s
        russian doll:     3322.0 i/s - 46.19x  (± 0.00) slower
        feed looping:     1020.1 i/s - 150.43x  (± 0.00) slower
      cached_comment:      803.3 i/s - 191.03x  (± 0.00) slower
      latest_comment:      606.5 i/s - 253.01x  (± 0.00) slower
     sorted_comments:      317.9 i/s - 482.77x  (± 0.00) slower
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Scenario 2: Posts: 20, Comments: 10 per post&lt;/h3&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Memory Benchmark
---------
Calculating -------------------------------------
     sorted_comments   586.444k memsize (    15.950k retained)
                         6.930k objects (   146.000  retained)
                        50.000  strings (    50.000  retained)
      latest_comment   116.855k memsize (     2.065k retained)
                         1.426k objects (    16.000  retained)
                        50.000  strings (    15.000  retained)
        feed looping    55.176k memsize (     0.000  retained)
                       702.000  objects (     0.000  retained)
                        50.000  strings (     0.000  retained)
         cached feed    55.136k memsize (    35.840k retained)
                       701.000  objects (   390.000  retained)
                        50.000  strings (    45.000  retained)
      cached_comment    70.032k memsize (    40.000  retained)
                       883.000  objects (     1.000  retained)
                        50.000  strings (     1.000  retained)
        russian doll   488.744k memsize (     1.304k retained)
                         7.808k objects (    24.000  retained)
                        50.000  strings (    20.000  retained)

Comparison:
         cached feed:      55136 allocated
        feed looping:      55176 allocated - 1.00x more
      cached_comment:      70032 allocated - 1.27x more
      latest_comment:     116855 allocated - 2.12x more
        russian doll:     488744 allocated - 8.86x more
     sorted_comments:     586444 allocated - 10.64x more

Benchmark ips
---------
Warming up --------------------------------------
     sorted_comments    25.000  i/100ms
      latest_comment    52.000  i/100ms
        feed looping    95.000  i/100ms
         cached feed    15.271k i/100ms
      cached_comment    78.000  i/100ms
        russian doll   325.000  i/100ms
Calculating -------------------------------------
     sorted_comments    240.019  (± 5.0%) i/s -      1.200k in   5.011152s
      latest_comment    532.712  (± 4.1%) i/s -      2.704k in   5.085499s
        feed looping    987.484  (± 2.8%) i/s -      4.940k in   5.006529s
         cached feed    156.682k (± 1.7%) i/s -    794.092k in   5.069703s
      cached_comment    744.650  (± 7.4%) i/s -      3.744k in   5.059516s
        russian doll      3.135k (± 4.9%) i/s -     15.925k in   5.092878s

Comparison:
         cached feed:   156681.7 i/s
        russian doll:     3134.7 i/s - 49.98x  (± 0.00) slower
        feed looping:      987.5 i/s - 158.67x  (± 0.00) slower
      cached_comment:      744.7 i/s - 210.41x  (± 0.00) slower
      latest_comment:      532.7 i/s - 294.12x  (± 0.00) slower
     sorted_comments:      240.0 i/s - 652.79x  (± 0.00) slower
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Scenario 3: Posts: 20, Comments: 50 per post&lt;/h3&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Memory Benchmark
---------
Calculating -------------------------------------
     sorted_comments     1.408M memsize (    15.950k retained)
                        17.330k objects (   146.000  retained)
                        50.000  strings (    50.000  retained)
      latest_comment   116.855k memsize (     2.065k retained)
                         1.426k objects (    16.000  retained)
                        50.000  strings (    15.000  retained)
        feed looping    55.176k memsize (     0.000  retained)
                       702.000  objects (     0.000  retained)
                        50.000  strings (     0.000  retained)
         cached feed    55.136k memsize (    35.840k retained)
                       701.000  objects (   390.000  retained)
                        50.000  strings (    45.000  retained)
      cached_comment    70.032k memsize (    40.000  retained)
                       883.000  objects (     1.000  retained)
                        50.000  strings (     1.000  retained)
        russian doll   488.744k memsize (     1.304k retained)
                         7.808k objects (    24.000  retained)
                        50.000  strings (    20.000  retained)

Comparison:
         cached feed:      55136 allocated
        feed looping:      55176 allocated - 1.00x more
      cached_comment:      70032 allocated - 1.27x more
      latest_comment:     116855 allocated - 2.12x more
        russian doll:     488744 allocated - 8.86x more
     sorted_comments:    1408084 allocated - 25.54x more

Benchmark ips
---------
Warming up --------------------------------------
     sorted_comments     8.000  i/100ms
      latest_comment    29.000  i/100ms
        feed looping    92.000  i/100ms
         cached feed    15.385k i/100ms
      cached_comment    76.000  i/100ms
        russian doll   343.000  i/100ms
Calculating -------------------------------------
     sorted_comments     87.033  (± 3.4%) i/s -    440.000  in   5.060406s
      latest_comment    287.003  (± 3.8%) i/s -      1.450k in   5.060856s
        feed looping    877.671  (± 3.0%) i/s -      4.416k in   5.036340s
         cached feed    152.899k (± 2.3%) i/s -    769.250k in   5.033712s
      cached_comment    719.988  (± 5.6%) i/s -      3.648k in   5.084290s
        russian doll      3.284k (± 6.0%) i/s -     16.464k in   5.035281s

Comparison:
         cached feed:   152899.4 i/s
        russian doll:     3284.4 i/s - 46.55x  (± 0.00) slower
        feed looping:      877.7 i/s - 174.21x  (± 0.00) slower
      cached_comment:      720.0 i/s - 212.36x  (± 0.00) slower
      latest_comment:      287.0 i/s - 532.75x  (± 0.00) slower
     sorted_comments:       87.0 i/s - 1756.80x  (± 0.00) slower
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Scenario 4: Posts: 20, Comments: 100 per post&lt;/h3&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Memory Benchmark
---------
Calculating -------------------------------------
     sorted_comments     2.434M memsize (    15.950k retained)
                        30.330k objects (   146.000  retained)
                        50.000  strings (    50.000  retained)
      latest_comment   116.855k memsize (     2.065k retained)
                         1.426k objects (    16.000  retained)
                        50.000  strings (    15.000  retained)
        feed looping    55.176k memsize (     0.000  retained)
                       702.000  objects (     0.000  retained)
                        50.000  strings (     0.000  retained)
         cached feed    55.136k memsize (    35.840k retained)
                       701.000  objects (   390.000  retained)
                        50.000  strings (    45.000  retained)
      cached_comment    70.032k memsize (    40.000  retained)
                       883.000  objects (     1.000  retained)
                        50.000  strings (     1.000  retained)
        russian doll   488.744k memsize (     1.304k retained)
                         7.808k objects (    24.000  retained)
                        50.000  strings (    20.000  retained)

Comparison:
         cached feed:      55136 allocated
        feed looping:      55176 allocated - 1.00x more
      cached_comment:      70032 allocated - 1.27x more
      latest_comment:     116855 allocated - 2.12x more
        russian doll:     488744 allocated - 8.86x more
     sorted_comments:    2433508 allocated - 44.14x more

Benchmark ips
---------
Warming up --------------------------------------
     sorted_comments     4.000  i/100ms
      latest_comment    19.000  i/100ms
        feed looping    79.000  i/100ms
         cached feed    15.535k i/100ms
      cached_comment    85.000  i/100ms
        russian doll   353.000  i/100ms
Calculating -------------------------------------
     sorted_comments     47.354  (± 4.2%) i/s -    240.000  in   5.075750s
      latest_comment    185.682  (± 2.2%) i/s -    931.000  in   5.016461s
        feed looping    754.015  (± 1.7%) i/s -      3.792k in   5.030455s
         cached feed    154.707k (± 1.1%) i/s -    776.750k in   5.021351s
      cached_comment    819.179  (± 2.0%) i/s -      4.165k in   5.086336s
        russian doll      3.390k (± 2.6%) i/s -     17.297k in   5.104940s

Comparison:
         cached feed:   154707.3 i/s
        russian doll:     3390.5 i/s - 45.63x  (± 0.00) slower
      cached_comment:      819.2 i/s - 188.86x  (± 0.00) slower
        feed looping:      754.0 i/s - 205.18x  (± 0.00) slower
      latest_comment:      185.7 i/s - 833.18x  (± 0.00) slower
     sorted_comments:       47.4 i/s - 3267.07x  (± 0.00) slower
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Scenario 5: Posts: 20, Comments: 1000 per post&lt;/h3&gt;
&lt;pre&gt;&lt;code class="highlight plaintext"&gt;Memory Benchmark
---------
Calculating -------------------------------------
     sorted_comments    20.977M memsize (    15.950k retained)
                       264.330k objects (   146.000  retained)
                        50.000  strings (    50.000  retained)
      latest_comment   116.855k memsize (     2.065k retained)
                         1.426k objects (    16.000  retained)
                        50.000  strings (    15.000  retained)
        feed looping    55.176k memsize (     0.000  retained)
                       702.000  objects (     0.000  retained)
                        50.000  strings (     0.000  retained)
         cached feed    55.136k memsize (    35.840k retained)
                       701.000  objects (   390.000  retained)
                        50.000  strings (    45.000  retained)
      cached_comment    70.032k memsize (    40.000  retained)
                       883.000  objects (     1.000  retained)
                        50.000  strings (     1.000  retained)
        russian doll   488.744k memsize (     1.304k retained)
                         7.808k objects (    24.000  retained)
                        50.000  strings (    20.000  retained)

Comparison:
         cached feed:      55136 allocated
        feed looping:      55176 allocated - 1.00x more
      cached_comment:      70032 allocated - 1.27x more
      latest_comment:     116855 allocated - 2.12x more
        russian doll:     488744 allocated - 8.86x more
     sorted_comments:   20977100 allocated - 380.46x more

Benchmark ips
---------
Warming up --------------------------------------
     sorted_comments     1.000  i/100ms
      latest_comment    22.000  i/100ms
        feed looping    25.000  i/100ms
         cached feed    14.818k i/100ms
      cached_comment    57.000  i/100ms
        russian doll   288.000  i/100ms
Calculating -------------------------------------
     sorted_comments      4.665  (± 0.0%) i/s -     24.000  in   5.177711s
      latest_comment    206.056  (± 7.3%) i/s -      1.034k in   5.050651s
        feed looping    239.214  (± 4.2%) i/s -      1.200k in   5.026543s
         cached feed    146.296k (± 5.1%) i/s -    740.900k in   5.079435s
      cached_comment    706.969  (±10.8%) i/s -      3.534k in   5.064015s
        russian doll      3.235k (± 4.9%) i/s -     16.416k in   5.087702s

Comparison:
         cached feed:   146296.1 i/s
        russian doll:     3235.0 i/s - 45.22x  (± 0.00) slower
      cached_comment:      707.0 i/s - 206.93x  (± 0.00) slower
        feed looping:      239.2 i/s - 611.57x  (± 0.00) slower
      latest_comment:      206.1 i/s - 709.98x  (± 0.00) slower
     sorted_comments:        4.7 i/s - 31362.27x  (± 0.00) slower
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;So&amp;hellip; What fix should you pick?&lt;/h2&gt;

&lt;p&gt;I think that you can use the simplest solution, &lt;a href="/association-with-default-order-to-fix-the-latest-comment-n-1-problem.html"&gt;&amp;ldquo;the association with default order&amp;rdquo;&lt;/a&gt;,
maybe until you are in the 50 comments per post, after that maybe you could try other options like
&lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html#russina_doll_caching"&gt;&amp;ldquo;fragment caching&amp;rdquo;&lt;/a&gt; or
&lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html#russina_doll_caching#looping"&gt;&amp;ldquo;looping through the latest comment for each post&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Download a cheatsheet&lt;/h2&gt;

&lt;p&gt;You can also download a cheatsheet to help you decide which fix to
pick in different contexts, based mostly on my interpretation of the benchmarks
and the complexity of each solution.&lt;/p&gt;

&lt;p&gt;Get it here: &lt;a href="/cheatsheet-to-help-you-decide-which-fix-for-the-last-comment-n-1-problem-is-the-best-for-you.html"&gt;
Cheatsheet for the &amp;ldquo;latest-comment&amp;rdquo; n+1 queries problem
&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>5 ways to fix the latest-comment n+1 problem</title>
    <link rel="alternate" href="https://bhserna.com/5-ways-to-fix-the-latest-comment-n-1-problem"/>
    <id>https://bhserna.com/5-ways-to-fix-the-latest-comment-n-1-problem</id>
    <published>2020-10-20T22:32:00Z</published>
    <updated>2020-10-20T22:32:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you have a method that works fine with a single record, but when you use it
on a list causes N+1 queries?&lt;/p&gt;

&lt;p&gt;One example of this problem is trying to get the latest comment on a list of
posts, but there are others, like the last review in a list of products, or
the cheapest price, etc…&lt;/p&gt;

&lt;p&gt;But let’s explain the problem with&amp;hellip;&lt;/p&gt;

&lt;h2 id="example"&gt;The latest comment on a list of posts&lt;/h2&gt;

&lt;p&gt;Imagine, that you have a model like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&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&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&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;p&gt;When you are fetching a single post there is no problem…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when you try to fetch a list of posts, the method &lt;a href="/why-active-record-seems-to-ignore-your-includes-and-runs-a-query-for-each-record.html"&gt;seems to ignore the
&lt;code&gt;includes&lt;/code&gt;&lt;/a&gt;
and runs a query for each post to get the &lt;code&gt;latest_comment&lt;/code&gt; for each post!&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can write a different method to get the &lt;code&gt;latest_comment&lt;/code&gt; with ruby (not
ActiveRecord), but it feels inelegant, and maybe it can be confusing later…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&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;:created_at&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The good news is that there are other solutions =)&amp;hellip; And I will share with you
some of them. So, you can select the one that is better for the current state
of your app.&lt;/p&gt;

&lt;h2 id="default_order"&gt;1. A default order for the &amp;ldquo;has_many&amp;rdquo; association&lt;/h2&gt;

&lt;p&gt;Instead of sorting the comments on the &lt;code&gt;latest_comment&lt;/code&gt; method, you can sort
the comments at the association level and just ask for the &lt;code&gt;last&lt;/code&gt; sorted
comment.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="has_one_latest_comment"&gt;2. A &amp;ldquo;has_one&amp;rdquo; association for the latest comment&lt;/h2&gt;

&lt;p&gt;Instead of loading all the comments for all the posts, you can use a &lt;code&gt;has_one&lt;/code&gt;
association for the &lt;code&gt;latest_comment&lt;/code&gt;, providing a scope with just the
&lt;code&gt;latest_comment&lt;/code&gt; for each &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
    &lt;span class="n"&gt;latest_comments_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;latest_comments_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="looping"&gt;3. Looping through the latest comment for each post&lt;/h2&gt;

&lt;p&gt;You can query the database for a list with the &lt;code&gt;latest_comment&lt;/code&gt; for each &lt;code&gt;post&lt;/code&gt;,
group them by &lt;code&gt;post_id&lt;/code&gt; and then pick the right &lt;code&gt;comment&lt;/code&gt; for each &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also use some specific objects the task, If you want to avoid polluting
you &lt;code&gt;Post&lt;/code&gt; and &lt;code&gt;Comment&lt;/code&gt; models.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
    &lt;span class="n"&gt;latest_comments_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;latest_comments_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Feed&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;posts&lt;/span&gt;
    &lt;span class="n"&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;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_by&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;:post_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="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;FeedPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;[&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;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&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;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeedPost&lt;/span&gt;
  &lt;span class="kp"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:latest_comment&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;
    &lt;span class="vi"&gt;@latest_comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latest_comment&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;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="russina_doll_caching"&gt;4. Russian doll-caching&lt;/h2&gt;

&lt;p&gt;If you are already using fragment cache on your app, you can embrace the
&amp;ldquo;Russian doll caching&amp;rdquo; and &lt;a href="https://www.youtube.com/watch?v=ktZLpjCanvg"&gt;think of n+1 as a feature&lt;/a&gt;, and let the n+1
queries run just the first time and use the cache after that.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&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;:id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&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;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In your view…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="c1"&gt;#posts/index.html.erb &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;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'posts/post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;collection: &lt;/span&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;cached: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="c1"&gt;#posts/_post.html.erb &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;cache&lt;/span&gt; &lt;span class="n"&gt;post&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;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&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;h2 id="caching_latest_comment"&gt;5. Caching the latest comment&lt;/h2&gt;

&lt;p&gt;You can also add a new column for the &lt;code&gt;last_comment_id&lt;/code&gt; and a &lt;code&gt;belongs_to&lt;/code&gt;
association, and keep the last comment in sync &amp;ldquo;manually&amp;rdquo; (maybe with an
&lt;code&gt;after_commit&lt;/code&gt; callback).&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:latest_comment_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&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;pre&gt;&lt;code class="highlight ruby"&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;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="s2"&gt;"Comment"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Comment&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;
  &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;update_latest_comment&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_latest_comment&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;latest_comment_id: &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:latest_comment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="other_query_options"&gt;Other options for the &amp;ldquo;latest comments for posts&amp;rdquo; query&lt;/h2&gt;

&lt;p&gt;You can try other options for the query. I used one that worked on postgres and sqlite3, but
maybe you want to try other depending on your database.&lt;/p&gt;

&lt;p&gt;This work on sqlite3 and postgres (used in the examples)&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
  &lt;span class="n"&gt;latest_comments_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max(id)"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;latest_comments_ids&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;p&gt;This works on postgres&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;arel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct_on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arel_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments"&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;p&gt;And this works on sqlite3&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comments_for_posts&lt;/span&gt;
  &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: :desc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:post_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="benchmark"&gt;Do you want to see some benchmarks?&lt;/h2&gt;

&lt;p&gt;If you want to see how this fixes behave in different scenarios, you can take a
look to the &lt;a href="benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;benchmarks for this 5 fixes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Or play with the code for those benchmarks. You can see the code on github at
&lt;a href="https://github.com/bhserna/latest_comment_n-1_fixes_benchmark"&gt;bhserna/latest_comment_n-1_fixes_benchmark&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="code"&gt;Do you want to run the examples?&lt;/h2&gt;

&lt;p&gt;You can see the code on github at &lt;a href="https://github.com/bhserna/latest_comment_n-1_examples"&gt;bhserna/latest_comment_n-1_examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use it to run the examples and play with the code and seed values,
to pick the right solution for you current case.&lt;/p&gt;

&lt;h2 id="pick_one"&gt;Now you can pick one&lt;/h2&gt;

&lt;p&gt;Now you know 5 difference ways to solve the problem, and you can pick the
one that better fits your app =)&lt;/p&gt;

&lt;p&gt;If you are not sure, I think you can use the first one, &lt;a href="/association-with-default-order-to-fix-the-latest-comment-n-1-problem.html"&gt;&amp;ldquo;the association with
default order&amp;rdquo;&lt;/a&gt;, unless your metrics are telling you that you need something
different.&lt;/p&gt;

&lt;h2&gt;Download a cheatsheet&lt;/h2&gt;

&lt;p&gt;You can also download a cheatsheet to help you decide which fix to
pick in different contexts, based mostly on my interpretation of the benchmarks
and the complexity of each solution.&lt;/p&gt;

&lt;p&gt;Get it here: &lt;a href="/files/cheatsheet_latest_comment_n_plus_1_fixes.pdf"&gt;
Cheatsheet for the &amp;ldquo;latest-comment&amp;rdquo; n+1 queries problem
&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Create a new controller for the hidden abstraction and reduce conditionals</title>
    <link rel="alternate" href="https://bhserna.com/create-a-new-controller-for-the-hidden-abstraction-and-reduce-conditionals"/>
    <id>https://bhserna.com/create-a-new-controller-for-the-hidden-abstraction-and-reduce-conditionals</id>
    <published>2020-10-06T22:51:00Z</published>
    <updated>2020-10-06T22:51:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Have you ever implemented a feature that does work, but has resulted in using multiple conditionals in your controller and views&amp;hellip; Wondering if there is a better way to do what you just did?&lt;/p&gt;

&lt;p&gt;Maybe your problem is that you have a hidden abstraction and you need a new controller…&lt;/p&gt;

&lt;h2&gt;Do you have a hidden abstraction?&lt;/h2&gt;

&lt;p&gt;Not always, but some times, you will be able to simplify your code by creating a new abstraction&amp;hellip; and a lot of the times you already know the name of it, and you are calling it by its name, but is not expressed in the code.&lt;/p&gt;

&lt;p&gt;For example imagine that you are building an app that lets you create &amp;ldquo;Invoices&amp;rdquo;, and you want to add the possibility to create &amp;ldquo;Estimates&amp;quot;… but those estimates are not a new kind of record they are &amp;quot;just simple invoice records&amp;rdquo;, but with an &lt;code&gt;estimate&lt;/code&gt; boolean attribute set to true.&lt;/p&gt;

&lt;p&gt;Something like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&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;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;line_items&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;estimate: &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;p&gt;And to implement the feature you will have, in you views… a &amp;ldquo;New invoice&amp;rdquo; button and a &amp;ldquo;New estimate&amp;rdquo; button and both send you to the invoice controller like this (not a big trouble yet)…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s2"&gt;"New invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_invoice_path&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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"New estimate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_invoice_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;estimate: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the controller something like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;if&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;:estimate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="vi"&gt;@invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;estimate: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="vi"&gt;@invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&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;p&gt;And on your other views (show, form, and mailer views)…. You will have this all over the place…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Estimate&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Invoice&lt;span class="nt"&gt;&amp;lt;/h1&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;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;estimate?&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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"Cancel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoices_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;estimate: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&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="k"&gt;else&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;link_to&lt;/span&gt; &lt;span class="s2"&gt;"Cancel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoices_path&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;p&gt;Is this your case?&lt;/p&gt;

&lt;h2&gt;If this is your case, then create a new controller&lt;/h2&gt;

&lt;p&gt;If you already are have a name for this thing, &amp;ldquo;Estimates&amp;rdquo;, you already have an abstraction, and maybe the first step that you can do is to create a new controller for it&amp;hellip;&lt;/p&gt;

&lt;p&gt;For example in this case you could have…&lt;/p&gt;

&lt;p&gt;A &amp;ldquo;New estimate&amp;rdquo; button to a new dedicated route…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;invoices&lt;/span&gt;
&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;estimates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s2"&gt;"New estimate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_estimate_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the controller you will need just one object, and you will be able to use the expected name&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="vi"&gt;@estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;estimate: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And for you other views (show, form, and mailer views)…. You won&amp;rsquo;t need the conditionals any more&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Estimate&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="highlight erb"&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s2"&gt;"Cancel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimates_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Maybe you can think that you are repeating to much… but normally &lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction"&gt;duplication is far cheaper than the wrong abstraction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Give it a try =)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Association with default order, to fix the latest-comment n+1 problem</title>
    <link rel="alternate" href="https://bhserna.com/association-with-default-order-to-fix-the-latest-comment-n-1-problem"/>
    <id>https://bhserna.com/association-with-default-order-to-fix-the-latest-comment-n-1-problem</id>
    <published>2020-09-30T12:19:00Z</published>
    <updated>2020-09-30T12:19:00Z</updated>
    <author>
      <name>Benito Serna</name>
    </author>
    <content type="html">&lt;p&gt;Do you have a method that works fine with a single record, but when you use it on a list causes N+1 queries?&lt;/p&gt;

&lt;p&gt;Maybe, you are building a rails app and you want to fetch a list of posts with the latest comment (or something similar)…&lt;/p&gt;

&lt;p&gt;You have a model like this…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Posts&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&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&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&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;p&gt;When you are fetching a single post there is no problem…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="n"&gt;post&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But when you try to fetch a list of posts, the method seems to ignore the &lt;code&gt;includes&lt;/code&gt; and runs a query for each post to get the latest comment for each post =(.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can write a different method to get the latest comment with ruby (and not ActiveRecord), but it feels weird, and you know that it can bring you troubles later =(.&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;latest_comment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&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;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&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;:created_at&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Is this the only solution?&lt;/p&gt;

&lt;p&gt;Well, nop… there are many ways to solve this =)&lt;/p&gt;

&lt;p&gt;And although ther is no perfect solution, I want to tell you the one that I think that you should try first…&lt;/p&gt;

&lt;h2&gt;A default order for the association&lt;/h2&gt;

&lt;p&gt;On the definition of your association, you will pass a scope with the default order. It looks like this&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;pre&gt;&lt;code class="highlight ruby"&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;:comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you are not sure about the default order in the association, you can create a new association…&lt;/p&gt;
&lt;pre&gt;&lt;code class="highlight ruby"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&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="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;class_name: &lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="no"&gt;Comment&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;latest_comment&lt;/span&gt;
    &lt;span class="n"&gt;sorted_comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&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;pre&gt;&lt;code class="highlight ruby"&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;:sorted_comments&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="nb"&gt;puts&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;latest_comment&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to know more about how this part of the &lt;code&gt;has_many&lt;/code&gt; association work, you can check with the &amp;ldquo;Scopes&amp;rdquo; section &lt;a href="https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many"&gt;in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope this tip, can help you =)&lt;/p&gt;

&lt;p&gt;Note: As as I said, this solution is not perfect, it can cause some performance
problems if you have many comments per posts, because you are preloading all
the coments for the list of posts, but don’t worry for now &lt;a href="/benchmarks-for-the-fixes-to-the-latest-comment-n-1-problem.html"&gt;unless you will have
many (maybe more than 50) comments per post&lt;/a&gt; =)&lt;/p&gt;

&lt;h2&gt;Want to know more ways to solve this problem?&lt;/h2&gt;

&lt;p&gt;If you want to review other options, you can see &lt;a href="/5-ways-to-fix-the-latest-comment-n-1-problem.html"&gt;this post with other 4 ways to fix this problem&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
</feed>
