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_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.