articles Rails Caching Again

04 May 2014
In the previous post I covered how can you use Rails' Russian Doll caching to make you app super fast. I didn't cover though how to cache search result pages and paginated results, so here comes the second part of that article. I made a sample application where I have a product listing page with pagination and a search form: (https://github.com/gregmolnar/rails-caching. Caching of the individual products is simple:
# app/views/products/index.html.erb
<% cache(product) do %>
  <tr>
    <td><%= product.name %></td>
    <td><%= product.price %></td>
    <td><%= link_to 'Edit', edit_product_path(product) %></td>
    <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
But we want to cache the full list too so we need to generate a cache key by ourself. My solution to this problem is to pluck the ids, join them and add the max updated_at value to the end of the string:
# app/helpers/products_helper.rb
module ProductsHelper
  def cache_key_for_products(products)
    ids = products.pluck(:id).join('-')
    max_updated_at = products.pluck(:updated_at).max
    "products/#{ids}-#{max_updated_at.to_i}"
  end
end
Now we can cache a bigger fragment in the view:
# app/views/products/index.html.erb
<%= cache(cache_key_for_products(@products)) do %>
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Price</th>
        <th colspan="2"></th>
      </tr>
    </thead>

    <tbody>
      <% @products.each do |product| %>
        <% cache(product) do %>
          <tr>
            <td><%= product.name %></td>
            <td><%= product.price %></td>
            <td><%= link_to 'Edit', edit_product_path(product) %></td>
            <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
          </tr>
        <% end %>
      <% end %>
    </tbody>
  </table>
<% end %>
This methods works for pagination and sorting too, since it relies on the order of the ids. If there is a search functionality too, all we have to do is to pass a suffix to the helper method:
# app/helpers/products_helper.rb
module ProductsHelper
  def cache_key_for_products(products, suffix = '')
    ids = products.pluck(:id).join('-')
    max_updated_at = products.pluck(:updated_at).max
    "products/#{ids}-#{max_updated_at.to_i}#{suffix}"
  end
end
I would pass the attribute name and the value so if someone is searching for a product name 'Jewel':
cache_key_for_products(@products, "jewel=#{@search.jewel_eq}")
That's it for now. I hope you enjoyed the article. ## Update David Patrick([dponrails](https://twitter.com/dponrails)) did some benchmarks and it turned out `pluck` is pretty slow so here is a better performing alternative would be the usage of map:
# app/helpers/products_helper.rb
module ProductsHelper
  def cache_key_for_products(products, suffix = '')
    ids = products.map(&:id).join('-')
    max_updated_at = products.map(&id).max
    "products/#{ids}-#{max_updated_at.to_i}#{suffix}"
  end
end
Thanks David for the heads up!

Did you enjoy reading this? Sign up to the Rails Tricks newsletter for more content like this!

Or follow me on Twitter

Job listings

Post a Job for FREE!

Related posts