Kristis
Kristis

Reputation: 377

How make caching work with pagination and search form?

I working with ruby on rails and my products views is cached and I can find products with search form. I want to use pagination as well, but when added, it didn't worked and also search form. Found code line ,but not sure how to use it, code below.

<% cache ["v1-#{params[:page]-#{params[:q]", cache_key_for_products]
> do %>

My code

Index.html.erb

<div class="products">
  <div class="container">
    <center><h1>All products</h1></center>
    <div class="row">
      <% cache(cache_key_for_products) do %>
      <%= render @products %>
      <% end %>
    </div>
    <%= will_paginate @comments, :container => false %>
  </div>
</div>

_product.html.erb

<% cache product do %>
<div class="col-md-3 col-lg-3 col-sm-6 col-xs-12">
  <div class="product-block">
    <h4 id="product-name"><%= product.name %></h4>
    <%= link_to product_path(product), class: 'product_link' do %>
      <%= image_tag(product.image_url, class: "img-responsive") %>
    <% end %>
    <div class="price">
      <h4>&pound; <%= product.price %></h4>
    </div>
  </div>
</div>
<% end %>

products_helper.rb

module ProductsHelper
  def cache_key_for_products
    count          = Product.count
    max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
    "products/#{params[:q] || "all"}-#{count}-#{max_updated_at}#{signed_in? && current_user.admin? ? "-admin" : "normal"}"
  end
end

products_controller.rb

def index
    if params[:q].present?
      search_term = params[:q]
      if Rails.env.development?
        @products = Product.where("name LIKE ?", "%#{search_term}%")
      else
        @products = Product.where("name ilike ?", "%#{search_term}%")
      end
    else
      @products = Product.all.paginate(:page => params[:page], :per_page => 30)
    end
  end

Upvotes: 0

Views: 777

Answers (1)

joshweir
joshweir

Reputation: 5617

In your controller, looks like you are paginating correctly when not performing a search, but need to add the pagination to your search query also:

  def index
    if params[:q].present?
      search_term = params[:q]
      if Rails.env.development?
        @products = Product.where("name LIKE ?", "%#{search_term}%").paginate(:page => params[:page], :per_page => 30)
      else
        @products = Product.where("name ilike ?", "%#{search_term}%").paginate(:page => params[:page], :per_page => 30)
      end
    else
      @products = Product.all.paginate(:page => params[:page], :per_page => 30)
    end
  end

Also you are caching each search result set, this means that the same products could be potentially cached multiple times in many different searches. This will quickly bloat your cache. It would be better to cache each product once and fetch these products from cache regardless of the search.

I see you are caching each product (in _product.html.erb partial). In index.html.erb change the code to this:

<div class="products">
  <div class="container">
    <center><h1>All products</h1></center>
    <div class="row">
      <%= render @products, cache: true %>
    </div>
    <%= will_paginate @comments, :container => false %>
  </div>
</div>

This will take advantage of multi fetch fragment caching which Rails 5 has built in:

1.3.1 Collection caching

The render helper can also cache individual templates rendered for a collection. It can even one up the previous example with each by reading all cache templates at once instead of one by one. This is done by passing cached: true when rendering the collection:

<%= render partial: 'products/product', collection: @products, cached: true %> All cached templates from previous renders will be fetched at once with much greater speed. Additionally, the templates that haven't yet been cached will be written to cache and multi fetched on the next render.

Otherwise if you are < Rails 5, use the Multi Fetch Fragments gem to enable this functionality.

In index.html.erb you could modify the collection cache rendering to something this to use a custom cache key:

<%= render @products, cache: Proc.new{|item| [item, 'show']} %>

Upvotes: 1

Related Questions