articles Rails Caching Again

04 May 2014
Are you eager to elevate your security skills and safeguard your applications against cyber threats? I created a Rails Security course is designed specifically for developers like you who aim to build robust, secure Rails applications!
Buy my course: Security for Rails Developers.
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!

Or follow me on Twitter

Related posts