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 belongs_to
and 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.
Family Ties
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 has_many
different Book
objects associated with it, while each Book
object will belong_to
one specific Genre
.
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 Genre
object:
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 non_fiction
and 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 parent
and children
methods to get a full list of all the objects associated with a particular Genre
instance:
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 Genre
model:
1
|
|
And then, call the class method tree_view
, which takes in an attribute parameter:
1
|
|
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.
tl;dr?
- A lot of people seem to like the
ancestry
gem, but I think thatacts_as_tree
is a good one to start off with. If you need the extra functionality thatancestry
provides, then you can eventually level up to that. Another variation on theacts_as_tree
is theacts_as_sane_tree
gem, 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.