When working in Rails, it’s all the family – literally. No matter the size of your application, almost all of your objects are going to be related to each other. You can create however many objects you want because database rows are cheap, cheap, cheap! But the more objects you make, the harder it is to keep track of the other data that the object relates to (which is generally yet another object).
I found myself in such a predicament last week, when I had to make numerous objects relate to one other to create a tree structure. The obvious first approach was to use the
has_many relationship. But when I realized that I wanted some
Genre objects to belong to other
Genre objects, I ran into a problem. Depending solely on the ActiveRecord relationships turned out to be painful, messy, and complicated, and wouldn’t make my code very flexible or sustainable over time.
So I Googled around and found a handy plugin created by DHH himself called
acts_as_tree. This gem allows you to create a hierarchical structure of objects in your application and – to take it a step further – gives you a bunch of incredibly helpful methods. It even allows you to visualize your tree structure! Sound amazing? That’s because it is. And if you follow a few easy steps, you can use it in your application, too.
So, for this post I’ll continue working through my basic eCommerce Bookstore application, which I’ve been using as an example for my previous posts. I’ve already got some
Book objects, but as my store starts to grow, it’s going to be pretty hard to keep track of the different genres of
Book objects that I currently have available.
The first step to tackling this problem was easy: create
Genre objects, each of which
Book objects associated with it, while each
Book object will
belong_to one specific
But what about genres that are associated with and “descend from” other genres? Well, here’s where the delightful and easy-to-use
acts_as_tree gem comes in.
First things first: we’ll add
gem 'acts_as_tree' to our Gemfile.
Next, we need to add a column to our
Genre database. We can write a simple migration that will add a
parent_id integer to our database, which will allow us to find the parents and children of a
1 2 3 4 5
Finally, we’ll head over to our
Genre model, which is what we needs to act as a (family) tree. We need to add a single line in here, which implements the ActiveRecord plugin and specifies what we’ll be ordering our
Genre objects by:
1 2 3 4 5
Blood Is Thicker Than Water
Okay, now let’s see this baby in action! We can start by making a root
Genre object, and then giving it some children:
1 2 3 4
Cool, but our tree doesn’t really look like a tree yet. Let’s give our
fiction genres some children, grandchildren, and great-grandchildren of their own:
1 2 3 4 5 6 7
Damn. Okay, well now our tree should look look less like a sprout and more like this bad boy:
It Runs In The Family
Even though we’ve created all these parent-child relationships, what can we do with them, exactly? Well, a lot! You can call the
children methods to get a full list of all the objects associated with a particular
1 2 3 4 5 6 7 8 9 10 11 12 13
An important thing to note here is that the
children method will return an array of objects, even if there’s only one child! So if you’re trying to get one particular object, remember to call the
first method in the array, or search by a
Genre object’s specific
id to avoid annoying bugs.
Some other cool methods to try include:
leaves, a class method that will return all the “leaves” of the tree (in an array).
descendants, an instance method that will return all the children, and the children’s children of an object (in an array).
self_and_siblings, which returns the receiver object, as well as any siblings it may have (in an array).
default_tree_order, which returns all the objects listed in alphabetical order!
But the coolest feature of the
acts_as_tree gem is Tree View, which allows you to see a visualization of your entire tree. All we have to do view this magic is add this line to our
And then, call the class method
tree_view, which takes in an attribute parameter:
The resulting return value is pure flora magic:
1 2 3 4 5 6 7 8 9 10
Isn’t it so beautiful?! Doesn’t it make you feel like this:
Or maybe it’s just me.
- A lot of people seem to like the
ancestrygem, but I think that
acts_as_treeis a good one to start off with. If you need the extra functionality that
ancestryprovides, then you can eventually level up to that. Another variation on the
acts_as_sane_treegem, which is configured for PostgreSQL 8.4 and comes with some cool extra methods (but isn’t nearly as massive as ancestry).
- There are a lot of different ways to implement the
acts_as_tree. Check out this railscast on tree-based navigation using this gem/plugin (beware the date on this one, though!).
- There are a lot of different ways to deal with recursive data structures in Rails. Check out this in-depth look at the tried and tested options to learn more.