Imagine you dealing with project where you have categories, and products in those categories, clothes website as an example. You may have following tree of categories:
Women
Skinny...Men
Shoes...
DB structure is very simple — id, parent_id, title for categories and category_id in products. Pretty straightforward, you can easily get parent and/or children of a category.
But you need to consider — if you select JEANS, it means you want JEANS + Straight JEANS + Skinny JEANS and so on. To achieve that you need selected category id, and ids of its children. Its easy to get them with rails, just write a small custom scope for it.
But what if children have their own children and tree is much deeper ? You will have to write advanced queries, joins, recursions to get ids of children on all levels. Or imagine you have selected last category, and you want to get all existing parents for it, or you want siblings of selected category (all items on the selected level, like Shirts, Jeans, …). By writing custom scopes and methods for all such cases, you will end up with huge and messy model pretty quickly.
One of the best tools to deal with nested categories is gem Ancestry (https://github.com/stefankroes/ancestry), with a lot of helper scopes and methods, especially for cases mentioned above.
Ancestry is very easy to understand and use.
Example project here: https://github.com/gioch/ancestry_test.git
ancestry
to Gemfile.rails g migration add_ancestry_to_category ancestry:string:index
### it will add new column to categories, which will handle parent/child stuff
class Category < ApplicationRecordhas_ancestryend
Its easy to create subcategories, you just need to specify parent
on create:
women = Category.create(name: 'Women')women_bags = Category.create(name: 'Bags', parent: women)women_clothes = Category.create(name: 'Clothes', parent: women)
# Check seeds.rb inside example project for more seeds.
Ancestry’s helpers are working like magic. Use method children
to get subcategories, or child_ids
to get only an array of ids of children:
# category {Women}cat = Category.first
# [{Bags}, {Clothes}]child_objects = cat.children
# [2, 3]child_ids = cat.child_ids
But, as mentioned above, if tree is very deep, and you want all levels of children, use descendants
instead:
# [{Bags}, {Clothes}, {Shirts}, {Jeans}, {Straight}, {Skinny}]all_children = cat.descendants
# [2, 3, 4, 5, 6, 7]all_child_ids = cat.descendant_ids
If you have selected last category in a tree, and you want to get all parents, use ancestors
or ancestor_ids
:
# get category {Straight}cat = Category.find(7)
# get parent category objects [{Women}, {Clothes}, {Jeans}]all_parents = cat.ancestors
# get parent category ids [1, 3, 5]all_parent_ids = cat.ancestor_ids
# get category {Clothes}cat = Category.find(3)
# get child idsdescendant_ids
# include selected category id if neededdescendant_ids
<< cat.id
Product.where(category_id: descendant_ids
)
Ancestry has a lot of other helpful methods.
Check Ancestry’s github page here - https://github.com/stefankroes/ancestry
Check Railscast for ancestry — http://railscasts.com/episodes/262-trees-with-ancestry