having an e-commerce application under development i am trying to get my head around the following problem: I have my categories realized through the awesome_nested_set plugin.
Extending charlysisto 's answer, when you are dealing with has_and_belongs_to_many
categories, you will soon find out that the brute force perspective is quite slow... You need some kind of "middleware" to make it faster...
socjopata 's answer gives a good approach of how to improve this. So, you use the definition
def branch_ids
self_and_descendants.map(&:id).uniq
end
in your category.rb
file (model).
Then, for example, in your controller (example with pagination):
@category = Category.find... # whatever
branches = @category.branch_ids
@prodcats = ProductCategory.select('distinct product_id')
.where('category_id IN (?)',branches)
.order(:product_id)
.paginate(page: params[:page], :per_page => 25)
ids = @prodcats.map(&:product_id)
@products = Product.where('id IN (?)', ids)
Finally, in your view, you will need a little "technique" in order to have pagination. You will paginate on @prodcats
, not @products
...
<%= will_paginate @prodcats %>
<% @products.each do |product| %>
....
<% end %>
This approach is way faster, WHEN DEALING WITH many_to_many
, though not 100% politically correct, I must admit.
There are a several ways you could improve your code, but if you're looking for all products that belong to a category and children (descendants) of the category then why you won't do it like this:
def branch_ids
self_and_descendants.map(&:id).uniq
end
def all_products
Product.find(:all, :conditions => { :category_id => branch_ids } )
end
I've done some assumptions here, but I hope you get the idea.
The key with awesome_nested_set is to use a range in the lft column. Here's a code sample of how I do it with a direct association (category has_many articles)
module Category
extend ActiveSupport::Concern
included do
belongs_to :category
scope :sorted, includes(:category).order("categories.lft, #{table_name}.position")
end
module ClassMethods
def tree(category=nil)
return scoped unless category
scoped.includes(:category).where([
"categories.tree_id=1 AND categories.lft BETWEEN ? AND ?",
category.lft, category.rgt
])
end
end # ClassMethods
end
Then somewhere in a controller
@category = Category.find_by_name("fruits")
@articles = Article.tree(@category)
that will find all articles under the categories apples, oranges, bananas, etc etc. You should adapt that idea with a join on categorizations (but are you sure you need a many to many relationship here?)
Anyway I would try this :
class Product < AR
has_many :categorizations
def self.tree(category=nil)
return scoped unless category
select("distinct(products.id), products.*").
joins(:categorizations => :category).where([
"categories.lft BETWEEN ? AND ?", category.lft, category.rgt
])
end
end
Let me know if there's any gotcha