Organizing ActiveRecord Models into a Tree Structure with the Ancestry Gem in Rails

Giritharan's avatar

Giritharan

Introduction

When building applications, it’s common to encounter scenarios where records need hierarchical relationships, such as categories within categories, comments with replies, or organizational structures. Initially, one might opt for a traditional one-to-many relationship. However, as the depth of the hierarchy grows, managing and querying such structures can become increasingly complex.

This is where the Ancestry gem comes in. It allows ActiveRecord models in Rails to be structured hierarchically using the materialized path pattern, which is efficient for both storage and querying. In this blog post, I’ll guide you through the installation, setup, and usage of the Ancestry gem, along with examples of key methods it provides.


Installing and Setting Up Ancestry

To begin using the Ancestry gem, follow these steps:

  1. Add the gem to your Gemfile:

    gem 'ancestry'
  2. Run bundle install to install the gem.

  3. Generate a migration to add the ancestry column to your model:

    rails g migration add_ancestry_to_model ancestry:string:index
  4. Update the migration file as needed (for instance, you may specify collation if using PostgreSQL or MySQL).

  5. Run the migration:

    rails db:migrate
  6. Finally, modify your model to include the has_ancestry method:

    class Category < ActiveRecord::Base
      has_ancestry
    end

With the setup complete, you can now explore the various features and methods Ancestry offers.


Example: Building a Category Tree

Let’s assume we want to create a category structure where each category can have subcategories, and those subcategories can have their own subcategories.

root_category = Category.create(name: "Electronics")
sub_category = root_category.children.create(name: "Mobile Phones")
child_category = sub_category.children.create(name: "Smartphones")

Here, "Mobile Phones" is a child of "Electronics", and "Smartphones" is a child of "Mobile Phones", forming a simple tree structure.


Key Ancestry Methods

Here are some key methods Ancestry provides, with examples:

1. parent

Returns the parent of the current node, or nil if it’s a root node.

child_category.parent # => <Category id: 2, name: "Mobile Phones">

2. root

Returns the topmost ancestor of the node.

child_category.root # => <Category id: 1, name: "Electronics">

3. ancestors

Returns all ancestors of a node, starting from the root.

child_category.ancestors # => [<Category id: 1, name: "Electronics">, <Category id: 2, name: "Mobile Phones">]

4. children

Returns the immediate children of a node.

root_category.children # => [<Category id: 2, name: "Mobile Phones">]

5. descendants

Returns all children, grandchildren, and further descendants of a node.

root_category.descendants # => [<Category id: 2, name: "Mobile Phones">, <Category id: 3, name: "Smartphones">]

6. siblings

Returns nodes that share the same parent as the current node.

sub_category.siblings # => []

Adding a sibling:

sub_category2 = root_category.children.create(name: "Laptops")
sub_category.siblings # => [<Category id: 4, name: "Laptops">]

7. path

Returns the nodes from the root to the current node.

child_category.path # => [<Category id: 1, name: "Electronics">, <Category id: 2, name: "Mobile Phones">, <Category id: 3, name: "Smartphones">]

8. subtree

Returns all descendants, including the current node.

root_category.subtree # => [<Category id: 1, name: "Electronics">, <Category id: 2, name: "Mobile Phones">, <Category id: 3, name: "Smartphones">]

9. indirects

Returns all descendants of a node that are not immediate children.

root_category.indirects # => [<Category id: 3, name: "Smartphones">]

10. has_children?

Returns true if the node has any children, otherwise false.

root_category.has_children? # => true
child_category.has_children? # => false

11. is_root?

Checks if a node is a root node.

root_category.is_root? # => true
child_category.is_root? # => false

12. has_siblings?

Returns true if the node has any siblings.

sub_category2.has_siblings? # => true

13. child_of? and sibling_of?

These methods allow you to check relationships between nodes.

child_category.child_of?(sub_category) # => true
sub_category2.sibling_of?(sub_category) # => true

Conclusion

The Ancestry gem is a robust solution for managing hierarchical data in Rails. It simplifies the creation and management of tree structures and provides a suite of methods for navigating and querying nodes in your hierarchy. Whether you’re building a category system, organizational chart, or any other nested model, Ancestry can help keep your data organized and easy to work with.

If you haven't already, give the Ancestry gem a try to see how it streamlines managing hierarchical data!

References