Question

I want to categorize objects in multiple trees to reflect their characteristics and to build a navigation on.

So, given the following trees:

Category1
-Category-1-1
-Category-1-2

Category2
-Category-2-1
-Category-2-2
--Category-2-2-1

An object could e.g. belong to both Category-1-2 and to Category-2-2-1.

The goal is to be able to fetch all objects from the database

  • that belong to a certain category
  • that belong to a certain category or its decendants

A more practical example:

A category might have a hierarchy of 'Tools > Gardening Tools > Cutters'.

A second category: 'Hard objects > Metal objects > Small metal objects'

An object 'Pruners' would be categorized as belonging to 'Cutters' as well as 'Small metal objects'.

I want to be able to

  • retrieve all 'Gardening Tools' -> 'Pruners'
  • retrieve all Category children of 'Gardening Tools' -> 'Cutters'
  • retrieve all 'Hard objects' -> 'Pruners'
  • retrieve all 'Hard objects' that are also 'Cutters' -> 'Pruners'
  • retrieve all 'Soft objects' that are also 'Cutters' -> [] Any pointers? I have briefly looked at closure_tree, awesome_nested_sets etc., but I am not sure they are a good match.
Was it helpful?

Solution 2

I just did this and I chose not to use ancestry, but closure_tree because the author says it is faster and I agree with him. Know you need a `has_and_belongs_to_many' between Categories (which I like to call tags whenever I add multiple to a single object) and Objects.

Now the finders, the bad news is that without your own custom query you might not be able to do it with one. Using the gems methods you will do something like:

Item.joins(:tags).where(tags: {id: self_and_descendant_ids })

The code is clean and it executes two queries, one for the descendant_ids and another one in Objects. Slight variations of this, should give you what you need for all except the last. That one is tough and I haven't implemented it (I'm in the process).

For now, you will have to call tag.self_and_ancestor_ids on both (Query count: 2), all items in those tags (Query count: 4) and intersect. After this, some serious refactoring is needed. I think we need to write SQL to reduce the number of queries, I don't think Rails query interface will be enough.

Another reason I chose *closure_tree* was the use of parent_id, all siblings share it (just like any other Rails association) so it made it easier to interface with other gems (for example RankedModel to sort).

OTHER TIPS

Please note that the code here is all pseudo code.

I would use ancestry gem and would model your data with three model classes. This way your data is normalized and it's a good base to build on.

Category - ancestry tree
  has_may Memberships
  has_may Products through Memberships

Membership
  belongs_to Category
  belongs_to Products

Products
  has_may Memberships
  has_may Categories through Memberships

From there on you need to figure out how to perform the equerries efficiently. My way of doing this is to understand how to do it with SQL and then figure out how to express the queries with activercord's DSL.

Some resources:

Queries examples:

Find a category.

Category.find(category_id)

Find a category and include it's products inside the specified category.

Category.find(category_id).join(:memberships => :products)

Find a category's sub-tree ind include products

Category.subtree_of(category_id).join(:memberships => :products)

Find all categories a products belongs to.

Product.find(product_id).categories

I think you could go for one of the tree gems, personally I like Ancestry. Then make an association for each category to have many objects and each object can belong to many categories.

Have you stumbled on any problems already or are you just researching your options?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top