views:

97

answers:

1

Right now I'm using a has_and_belongs_to_many association for two of my models like so:

class Books < ActiveRecord::Base
    has_and_belongs_to_many :publishers
end

class Publisher < ActiveRecord::Base
    belongs_to :publishing_company
    has_and_belongs_to_many :books
end

You'll notice that each publisher belongs to a publishing company:

class PublishingCompany < ActiveRecord::Base
    has_many :publishers
end

My goal is to set up an association that would allow me to do:

PublishingCompany.find(1).books

Is this possible with conventional RoR associations?

+3  A: 

The concept you're looking for is specifying a second degree association using the :through parameter on a has_many association on your PublishingCompany class. Doing second degree associations (that will join 2 extra tables) are very common, I don't think I've ever performed a third degree association though (publishers -> publisher_books -> books), and if I recall right Rails gets fairly sloppy at understanding what you're trying to do once you push associations this far.

The first option that's worth trying is:

class PublishingCompany
  has_many :publishers
  has_many :books, :through => :publishers
end

The Rails documentation however states that a :through parameter can only be used on a has_many or belongs_to, meaning this shouldn't work through the has_and_belongs_to_many association you have.

Your second options is what I had to do on an early system I wrote on Rails 1. I'll likely get voted down for this, but it was something I had to cook up since I couldn't get rails to handle it.

Since you're only going to use the association in a read only fashion, I just created a fake method to handle it. Be warned, this is a last resort. As a side note I personally dislike has_and_belongs_to_many associations as I find it strange that you don't have objects that you can manipulate that represent the rows of the join table.

class Books
  has_many :book_publishers
  has_many :publishers, :through => :book_publishers
end

class BookPublisher
  belongs_to :book
  belongs_to :publisher
end

class Publisher
  has_many :book_publishers  
  has_many :books, :through => :book_publishers
  belongs_to :publishing_company
end

class PublishingCompany
  has_many :publishers
  has_many :book_publishers, :through => :publishers

  def books
    book_publishers.map{|bp|bp.book}
  end
end

# typical use, eager loading to avoid N+1
company = PublishingCompany.first :include => {:book_publishers => :book}
company.books.each {|b| puts b.title}
Michael