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

by Giritharan, System Analyst

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:

    Ruby

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

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

    bash

    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:

    bash

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

    Ruby

    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.

Ruby

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.

Ruby

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

2. root

Returns the topmost ancestor of the node.

Ruby

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

3. ancestors

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

Ruby

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

4. children

Returns the immediate children of a node.

Ruby

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

5. descendants

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

Ruby

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.

Ruby

sub_category.siblings # => []

Adding a sibling:

Ruby

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.

Ruby

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.

Ruby

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.

Ruby

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

10. has_children?

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

Ruby

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

11. is_root?

Checks if a node is a root node.

Ruby

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

12. has_siblings?

Returns true if the node has any siblings.

Ruby

sub_category2.has_siblings? # => true

13. child_of? and sibling_of?

These methods allow you to check relationships between nodes.

Ruby

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

More articles

Protecting Your LLM Applications from Prompt Injection Attacks

Learn practical techniques to defend against prompt injection attacks in AI applications with simple code examples.

Read more

How to Read a Flame Graph in Chrome DevTools

A deep, practical guide to reading flame charts in Chrome DevTools, spotting expensive functions, and validating performance improvements.

Read more

Your competitors are already using AI.
The question is how fast you want to unlock the value.

Don't know where to start?

AI is everywhere but it's unclear which investments will actually move your metrics and which are expensive experiments.

Your data isn't ready

Most AI projects fail at the data layer. Pipelines, quality, access all need work before LLMs can deliver value.

Internal teams are stretched

Your engineers are shipping product. They don't have capacity to also become AI specialists with production-grade experience.

Legacy systems block everything

Aging, undocumented codebases make AI integration slow, risky, and expensive. They need to move first.

Don't worry. We've got you covered.

Start with the audit.