Sometimes, when you’re building a Rails application, you want to generate different relationships to the same model – or, rather, multiple relationships to multiple perspectives on a model. You can tell ActiveRecord to override the expected class name, but you can also use this technique to create “subsets” of classes for relationships, too. It’s worth remembering that ActiveRecord can generate these for you. By doing the query in the database, rather than filtering afterwards, your application will run faster.
As you can see from that explanation, I’m not exactly the greatest developer, so this is probably best explained with an example from life.
I’m currently working on a CMS – yawn yawn, I know. Anyhow, in this CMS, we have articles, and an Article has_many :comments
. Obvious one-to-many relationship, fine. However, comments may or may not be spam, marked by the attribute is_spam
. So whilst article.comments
will return all the comments on a particular article, it’d be nice to have a class method that returns all the clean comments; article.clean_comments
, for instance.
My first stab at this was to add the following method to the Article class:
def clean_comments
output = []
self.comments.each do |c|
output << c unless c.is_spam
end
output
end
which is, I suppose, passable idiomatic Ruby, and which does the job; we build an output hash of objects unless the comment is spam. The problem is that if you have, say, an article with a thousand comments - of which 990 are spam, we end up having to generate 1000 objects from the database, and then filter them with Ruby. It'd be faster to select only 10 from the database in the first place, right?
We can do that by defining a new relationship. In our Article model, under has_many :comments
, we can add this:
has_many :clean_comments,
:class_name => 'Comment',
:conditions => 'is_spam is null',
:order => 'created_at'
Bingo. That extra has_many
allows us to refer to article.clean_comments
, and it'll do so directly from the database via SQL. This is a fairly simple example, and I do recommend looking up has_many
and has_one
(which has similar capabilities) in the Rails API documentation. There always seems to be more depth with Rails than you initially expect, so it's worth digging a little deeper - and you'll make your code leaner, quicker, and better as a result.