paint-brush
Nested categories with rails gem Ancestry.by@gioch
8,968 reads
8,968 reads

Nested categories with rails gem Ancestry.

by George ChkhvirkiaOctober 17th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

<a href="https://hackernoon.com/tagged/imagine" target="_blank">Imagine</a> you dealing with <a href="https://hackernoon.com/tagged/project" target="_blank">project</a> where you have categories, and products in those categories, clothes website as an example. You may have following tree of categories:
featured image - Nested categories with rails gem Ancestry.
George Chkhvirkia HackerNoon profile picture

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

  • Bags
  • Clothes
    • Shirts
    • Jeans [SELECTED]
      • Straight


      • Skinny...Men

  • Accessories

  • 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.

Usage

Ancestry is very easy to understand and use.

Example project here: https://github.com/gioch/ancestry_test.git

  • Add gem ancestry to Gemfile.
  • Create model for categories (without parent_id or anything like that, ancestry will take care of it for us)
  • Generate migration to add ancestry field to categories:

rails g migration add_ancestry_to_category ancestry:string:index

### it will add new column to categories, which will handle parent/child stuff

  • Add `has_ancestry` to Categories model



class Category < ApplicationRecordhas_ancestryend

Creating categories and subcategories

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.

Getting all children for selected category

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

Getting all parents of selected category

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

Getting products of specified category


# 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.

Additional Resources

Check Ancestry’s github page here - https://github.com/stefankroes/ancestry

Check Railscast for ancestry — http://railscasts.com/episodes/262-trees-with-ancestry