I know I know, it's been close to a month since I last blogged — that's because I've been working and feeling hobby-lazy on the weekends, and I'm still in the middle of writing an approximately 6—8 page (single spaced!) essay, so I'm a little preoccupied…
Okay, what's the deal with Ancestry?
First off, it's a slick and polished gem — strike one. Second, it addresses a very common need — strike two. Finally, it is Rails 3 savvy and efficient — home run!
In all seriousness, Ancestry is perhaps one of the handiest gems I've used of late. Typically, setting up trees with ActiveRecord is relatively easy — but as the model logic grows, the ease of use/implementation fails. Query calls increase recursively with every layer of the tree, and scopes have to be manually called at each layer.
What's the Problem?
Take a simple example: setting up a comment model. Each comment
has_many :comments through the
parent_id; in addition, a comment might have a status of pending, published, or rejected. Pretty quickly this useful setup turns into a coding disaster: although it's relatively easy to setup a recursive partial to render the comment tree, we break a major rule of Rails convention — performing recursive queries from within the view layer. For large systems, this can result in hundreds of separate SQL queries for a single page view; first signal that we need a better solution.
Second, what if you want visitors to only see published comments? Simple enough, you can make a custom scope; but child comments rendered in the recursive partial know nothing about that scope, and ideally shouldn't — likely you'll want to see pending comments inline by logging in as a moderator. With minimal complexity, we've already had to introduce scope duplication and too much controller behavior into the view. Yuck!
And How Exactly Am I Supposed to Fix This?
Very easily: add
gem 'ancestry' to your Gemfile, run
bundle install, and execute
rails g migration add_ancestry_to_[model] ancestry:string. After you add
has_ancestry to your model, you can call a slew of methods, such as
ancestors to fetch the entire tree "limb," or
children to nab all the direct descendants. The neatest part about the ancestry gem however is that most of the methods are simply specialized scopes — which means you could fetch all of the comments from the controller without having to worry about the scope being applied to every layer of the tree.
Sweet, But Too Short — Where Do I Go Now?
Checkout the ancestry repository at github for more sweets. Happy coding!
Just so you know, this is my definition of "short," but for my readers' sakes I shall try to shorten my posts even more when appropriate. Got a shorty you want my take on? Let me know in the comments, or shoot me an email with your request.