ancestry vs. awesome_nested_set
There are several gems available for handling tree structures in Rails. This article will compare the following solutions:
Scenario
Suppose there is a model called Node that can have child nodes in a tree structure.
Features
ancestry
Setup
Using the new rules, add the following to config/initializers/ancestry.rb
1 | Ancestry.default_ancestry_format = :materialized_path2 |
Tables that need to support a tree structure should include some dedicated columns.
1 | create_table :nodes do |t| |
Then, add has_ancestry to the model.
1 | class Node < ApplicationRecord |
Basic Usage
List the commonly used instance methods
1 | node.children |
List the commonly used class methods
1 | Node.roots |
Ransack Search
Use class methods in combination with Ransack.
1 | def self.ransackable_attributes(_auth_object = nil) |
and then
1 | Node.ransack(children_of: 10) |
However, to search for roots, you need to query the ancestry column.
1 | Node.ransack(ancestry: '/') |
awesome_nested_set
Setup
Tables that need to support a tree structure should include some dedicated columns.
1 | create_table :nodes do |t| |
Then, add acts_as_nested_set to the model.
1 | class Node < ActiveRecord::Base |
Basic Usage
List the commonly used instance methods
1 | node.children |
If you want to include self, use a more explicit naming convention.
1 | node.self_and_ancestors |
List the commonly used class methods
1 | Node.roots |
Ransack Search
You can directly search using parent_id.
1 | def self.ransackable_attributes(_auth_object = nil) |
and then
1 | Node.ransack(parent_id: 10) |
If you need other types of searches, you can define your own scopes to use.
closure_tree
Setup
Unlike other gems, the setup process is relatively complex and should follow the official documentation step by step. In the end, an additional table will be created to record the tree structure. For example, with the Node model above, another table called node_hierarchies will be generated.
Basic Usage
It’s similar to awesome_nested_set, so I won’t repeat it here. However, compared to the first two gems, this one does not store children_count and depth in the database. They are calculated in real time. If you need those features, this gem might not be suitable.
Ransack Search
It’s similar to awesome_nested_set, so I won’t repeat it here.
Benchmark
Next, let’s test the performance. I created a benchmark project, and the results are approximately as follows (reorganized and formatted):
1 | --------------------------------- |
Although ancestry generally performs better in most cases, its most commonly used feature, children_of (retrieving child nodes by id), is actually the slowest. The official default implementation is about 20 times slower than awesome_nested_set. This is because when executing:
1 | Model.children_of(1) |
Its implementation is:
1 | Model.find(1).children |
It executes two queries. The similar class methods work this way. I implemented a single-query version as follows:
1 | def self.children_of_by_id(id) |
The performance improved by about 10 times, but it is still relatively slow.
Conclusions
We compare in a table:
| Item | ancestry | awesome_nested_set | closure_tree |
|---|---|---|---|
| Features | O | O | △ |
| Easy to use | O | O | △ |
| Performance | O | O | △ |
| Ransack Support | O | O | O |
A comprehensive comparison shows that both ancestry and awesome_nested_set have their strengths. ancestry uses less storage space for data (but node moves are slower since all child nodes need to be loaded and updated). awesome_nested_set generally offers better query performance. However, during my testing, I encountered a bug with ancestry that caused data corruption. This happened when model records were loaded into memory and nodes were moved multiple times to different positions.
Additionally, awesome_nested_set works more seamlessly with Ransack, allowing direct queries using parent_id, which is more straightforward. Therefore, I think awesome_nested_set might be the better choice.

