04 May 2014 By: Greg Molnar

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.

David Patrick(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!