3

I have these relationships

User
 has_many :products
 has_many :stores

Product
 belongs_to :user
 belongs_to :store
 belongs_to :category

Store
 belongs_to :user
 has_many :products

Category
 acts_as_nested_set
 has_many :products

In the homepage( view file ) i have a category drop down similar to amazon's:

 <ul id="site-category-dropdown">
            <li class="has-dropdown">
                <a href="#">
                    <span class="site-category-dropdown-link-span">
                        <span class="line-1">SHOP BY</span>
                        <span class="line-2">Category</span>
                    </span>

                </a>
                <ul class="dropdown dropdown-box-shadow"> 
                    <% Category.all.each do |root_cat| %>
                        <li class="has-dropdown site-category-dropdown-element">
                            <a href="#" class="site-category-dropdown-element-link"> 
                                <span class="term"><%= root_cat.name %></span>
                            </a>
                                <ul class="dropdown">
                                    <% root_cat.children.each do |children| %>
                                        <li><%= link_to children.name, category_path(id: children.id) %></li>
                                    <% end %>
                            </ul>
                        </li>
                    <% end %>
                </ul>
            </li>
        </ul>

This looks something like the image below ( Root categories and their sub categories is shown on hover) Site category dropdown

Now i'm on the store page, and i want to show a drop down similar to the site drop down but only for the products that are being sold by the store.
Store products

Product 1 - (category_id: 46, store_id: 1, product_name: "Prada t-shirt")
Product 2 - (category_id: 47, store_id: 1, product_name: "Prada shoes")
Product 3 - (category_id: 47, store_id: 1, product_name: "Gucci shoes")
Product 4 - (category_id: 12, store_id: 1, product_name: "A classy Dining Table")
Product 5 - (category_id: 12, store_id: 1, product_name: "Kitchen stool")
Product 6 - (category_id: 12, store_id: 1, product_name: "Office Chair")

<br>
cateogory_id 46 is T-shirt in Fashion -> Men -> T-shirt
<br>
category_id 47 is Shoe in Fashion -> Men -> Shoe
<br>
category_id 12 is Furniture in Home -> Furniture
<br>

I'm using the awesome_nested_set gem for the categories (https://github.com/collectiveidea/awesome_nested_set)
i can map all the category_id in an array using: category_ids = @store.products.map(&:category_id)

My question is, how can i build a drop down similar to the site drop down i showed above but only for products sold by this store. Remember the category_id for each products are the category ids for the leaf category, how do i recreate a drop down from the root categories? Using the store products I've given above, it should look something like this: Store category drop down, only products sold by the store.

Skyalchemist
  • 461
  • 1
  • 7
  • 18
  • You do realize how many queries this will execute right? I am not in a position right now to help you answer the question. I just wanted to point out the extreme n+1 issues I can see arising from this.(e.g. 1 query to collect all categories and then x queries for the children of those categories and then possibly x more queries against those children) As things expand this will have great performance impacts. – engineersmnky Sep 25 '14 at 16:50
  • Yeah, that's the problem. I was just wondering if someone has tackled this kind of problem before. – Skyalchemist Sep 25 '14 at 17:14
  • You can do: `Category.self_and_descendants ` just make sure your hierarchy of categories is only till something like: `home -> furniture -> table` or `Fashion -> men -> t-shirt -> (brand name)` – Surya Sep 25 '14 at 17:26
  • Sorry? How are you intending to get the Category? That's what the question is about. – Skyalchemist Sep 25 '14 at 17:36
  • @engineersmnky: i could get it once and cache it. The cache will expire when a new product is added to the store. – Skyalchemist Sep 25 '14 at 18:03

1 Answers1

2

This might be a naive implementation, but I think it could do the trick.

# in your controller
@categories = find_root_categories @store.products.map(&:category_id)

def find_root_categories(leaf_categories)
  leaf_categories.map { |node| find_root(node) }.uniq!
end

def find_root(leaf)
  return leaf unless leaf.parent_id?
  find_root(Category.find(leaf.parent_id))
end

And then you'd iterate over the collection just as you did in your original post. This does incur the overhead that @engineersmnky warned about though, as you'll be making quite a lot of database calls. It might be a good idea to cache all categories in an instance variable before calling find_root:

# in the controller
@categories = Category.all

def find_root(leaf)
  return leaf unless leaf.parent_id?
  find_root(@categories.find(leaf.parent_id))
end

Please let me know if I've misunderstood something about your question!

nicohvi
  • 2,270
  • 2
  • 28
  • 42