KazKazar
KazKazar

Reputation: 151

Rails & Ajax: display products by chosen filters

I need to display products by chosen categories using Ajax; if the user didn't choose a category, then all the products are displayed. If the user chose one or two categories, then only products that belong to these categories should be displayed.

Example:

enter image description here

My models Product, Category, and join table causes_products

Category

class Category < ActiveRecord::Base has_and_belongs_to_many :products end

Products

class Product < ActiveRecord::Base has_and_belongs_to_many :categories end


I was looking for a tutorial and I found "Rails 4: "How to partials & AJAX, dead easy". I followed this tutorial but I got an error. I feel like I missed something.
Can you please advise me on this or refer to a helpful source. Thank you!

products controller:

class ProductsController < ApplicationController
  def index
    @products = Product.all
    @categories = Category.all
  end
  def from_category
    @selected = Product.where(:category_id => params[:cat_id])
    respond_to do |format|
      format.js
    end
  end
end


products/index.html.erb:

<div class="grid">
  <%= render 'sidebar_menu' %>
  <%= render partial: 'product_grid', locals: { products: @products} %>
</div>


products/_sidebar_menu

<% @categories.each do | cat | %>
<%= link_to cat.name, fetch_products_path(:cat_id => cat.id), :remote => true %>
<% end %>


routes.rb

get "/fetch_products" => 'products#from_category', as: 'fetch_products'


products/_products_grid.html.erb

<div>
  <%= render partial: 'products_list', locals: { products: products } %>
</div>


products/_products_list.html.erb

<% products.each do |product| %>
    <div class="product_box">
      product.name
    </div>
<% end %>


products/from_category.js.erb

$("#products_grid").html("<%= escape_javascript(render partial: 'products_list', locals: { products: @selected } ) %>");


my terminal output:

Product Load (3.2ms)  SELECT `products`.* FROM `products` WHERE `products`.`category_id` = '11'
Mysql2::Error: Unknown column 'products.category_id' in 'where clause': SELECT `products`.* FROM `products` WHERE `products`.`category_id` = '11'
    Rendered products/_products_list.html.erb (8.8ms)
    Rendered products/from_category.js.erb (12.2ms)
    Completed 500 Internal Server Error in 23ms (ActiveRecord: 3.5ms)


ActionView::Template::Error (Mysql2::Error: Unknown column 'products.category_id' in 'where clause': SELECT `products`.* FROM `products` WHERE `products`.`category_id` = '11'):
    1: <% products.each do |product| %>
    2:     <div class="product_box">
    3:     <%= product.name %>
    4:     </div>
  app/views/products/_products_list.html.erb:1:in `_app_views_products__products_list_html_erb___255698018512654633_70336949043820'
  app/views/products/from_category.js.erb:1:in `_app_views_products_from_category_js_erb___4172137050793767106_70336965664480'
  app/controllers/products_controller.rb:24:in `from_category'`




* - originally sidebar_menu file contained only one line:
<%= link_to cat.name, fetch_items_path(:cat_id => cat.id), :remote => true %>
but I got this error:

Showing views/products/_sidebar_menu.html.erb where line #1 raised:

undefined local variable or method `cat' for #<#<Class:0x007ff1336747c0>:0x007ff1350376a8>
Extracted source (around line #1):

<%= link_to cat.name, fetch_products_path(:cat_id => cat.id), :remote
=> true %>




Update 1

@max's solution:

 def from_category
    @products = Product.joins(:categories).where(categories: { id: params[:cat_id] })
    respond_to do |format|
      format.js
    end

Thanks to @max for his help, his solution was to update controller action, it solved the error but the list of products isn't being refreshed after selecting a category.

log:

Started GET "/fetch_products?cat_id=11" for ::1 at 2016-04-18 13:53:01 +0300
Processing by ProductsController#from_category as JS
  Parameters: {"cat_id"=>"11"}
  Product Load (1.9ms)  SELECT `products`.* FROM `products` INNER JOIN `categories_products` ON `categories_products`.`product_id` = `products`.`id` INNER JOIN `categories` ON `categories`.`id` = `categories_products`.`category_id` WHERE `categories`.`id` = 11
  Rendered products/_products_list.html.erb (6.4ms)
  Rendered products/from_category.js.erb (11.5ms)
Completed 200 OK in 29ms (Views: 24.1ms | ActiveRecord: 1.9ms)




Update 2:

@max That's what I get in the response when I click on a category:

$("#products_grid").html("  
  <div class=\"product_box\">\n      Bed\n    <\/div>\n
  <div class=\"product_box\">\n      Notebook\n    <\/div>\n
  <div class=\"product_box\">\n      Desk\n    <\/div>\n    
  <div class=\"product_box\">\n      Mug\n    <\/div>\n"
);

@Jeffrey M Castro noticed that I was missing the products_grid id in the div:

products/_products_grid.html.erb

<div id="products_grid">
  <%= render partial: 'products_list', locals: { products: products } %>
</div>

The code works great only for one chosen category, but I would like it to be able to filter two or more categories as well. What changes do I need to make?

Scenario: User clicks the 'Home category', he gets all the products that belong to this category. Then he clicks on the 'Shoes category' and now he sees all the products that belong to both categories ('Home category' or 'Shoes category'). User clicks on the 'Clear All' button (in order to reset the chosen categories), now all the products from all the categories are displayed.

Thanks.

Upvotes: 0

Views: 658

Answers (2)

Jeffrey M Castro
Jeffrey M Castro

Reputation: 377

Following responses from @max, it is a good practice to check the JS Response that your server gives using the network tab of your browsers inspector.

In your code:

$("#products_grid").html("<%= escape_javascript(render partial: 'products_list', locals: { products: @selected } ) %>");  

You want to change the html content of #products_grid, but I do not see in your view files anything that has that ID.
So you have to specify in the div that its id is #products_grid:

products/_products_grid.html.erb

<div id="products_grid">
  <%= render partial: 'products_list', locals: { products: products } %>
</div>

Upvotes: 2

max
max

Reputation: 102036

Since you have a has_and_belongs_to_many relation between products and categories you cannot select products by products.category_id since the actual link between the two is stored in the categories_products join table.

You need to use a join and specify the condition on the association instead:

@products = Product.joins(:categories)
                   .where(categories: { id: params[:cat_id] })

Upvotes: 2

Related Questions