07 Oct 2009
If you work much with Rails and ActiveRecord you may have noticed that a certain commonly-used association is not allowed. I will show you how to simulate it. Let’s say you have the following models and relationships:
class Article < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
belongs_to :section
end
class Section < ActiveRecord::Base
has_many :categories
end
Now you want to find all the articles in a section, so you try the obvious solution, adding the following to Section:
has_many :articles, :though => :categories
This relationship seems reasonable enough, but an exception will be raised:
ActiveRecord::HasManyThroughSourceAssociationMacroError:
Invalid source reflection macro :has_and_belongs_to_many
for has_many :articles, :through => :categories.
Use :source to specify the source reflection.
Don’t waste time trying to find the right :source. The has_many association through a has_and_belongs_to_many is simply not allowed in Rails 2.3.4, but you can get the behavior you want by defining a Section#articles method that returns a nice ActiveRecord::NamedScope proxy. Just add this named scope to your Article class:
named_scope :in_section, lambda{ |section|
{
:joins => :categories,
:conditions => {:categories => {:id => section.category_ids}},
:select => "DISTINCT `articles`.*" # kill duplicates
}
}
and add this method to your Section class:
def articles
Article.in_section(self)
end
Now, since this is a pure named scope, you can do things like section.articles.published.recent. This technique also works in situations where you’re stopped by a polymorphic association. If you find yourself using this technique a lot you might want to abstract it to a plugin. Open source, please!