简体   繁体   中英

How to create a 'has_many through' association if a polymorphic association is involved?

I have 4 models: Model 1 associated with model 2, and model 2 with model 3. Models 2 and 3 are associated to model 4 with a polymorphic association. This all works. I'm having trouble specifying the association between model 1 and model 4, which are thus related in two ways:

  1. model 1 - model 2 - model 4
  2. model 1 - model 2 - model 3 - model 4

Below you'll find all the associations, except that between model 1 and model 4:


Author model:

has_many :books,     dependent: :destroy

Book model:

belongs_to :author
has_many   :chapters, dependent: :destroy
has_many   :titles,   as: :titlesource,       # Polymorphic Association
                      dependent: :destroy

Chapter model:

belongs_to :book
has_many   :titles,   as: :titlesource,       # Polymorphic Association
                      dependent: :destroy

Title model:

belongs_to :titlesource, polymorphic: true

Question: Suppose you have the above associations and you want to know the titles that an author has, irrespective whether they are a book-title or chapter-title. What associations should I expand the above setup with? (I assume something with has_many through )

Update: If it were only book-titles I would be looking for, I would expect adding has_many :titles, through: :books to the Author model to work. But it doesn't. If I add that association, then Author.first.titles produces the error undefined method 'titles' . I think this has to do with the polymorphic association and naming...? What am I doing wrong?

For chapter title it is even more difficult. That association goes through the Book model, then through the Chapter model and then to the Title model. How can I include these titles in the author model? I think it would have to be code with the following logic (but obviously this code is incorrect):

has_many :titles, through: :books && :books:chapters

Update: The two answers given use SQL. Do these solutions not send a lot of queries to the db, causing problems if the method is used often? Has one of these methods an advantage over the other in performance?

If you were to use an association on the Author like has_many :titles, through: :books it should normally work as long as you have set a titlesource_id and a titlesource_type on the Title model migration.

However, if you would add another one to fetch the titles through the chapters like this has_many :titles, through: :chapters it would overwrite the first one and it would fetch only the titles that are of titlesource_type = 'Chapter' .

Therefore the solution that I would suggest to you, if you don't mind sql is to implement a method like this in the Author model.

  def titles
    Title.where(
      "(titles.titlesource_type = 'Book'
      AND titles.titlesource_id
      IN (SELECT books.id FROM books WHERE books.author_id = #{id}))
      OR (titles.titlesource_type = 'Chapter'
      AND titles.titlesource_id
      IN (SELECT chapters.id
      FROM books
      INNER JOIN chapters ON chapters.book_id = books.id
      WHERE books.author_id = #{id}))"
    )
  end

If you don't need ActiveRecord methods (like where or order ), then this will give you an array of titles.

def titles_array  # sacrificing ActiveRecord methods
  books.collect { |b| b.titles + b.chapters.collect { |c| c.titles } }
end

You can still use AR methods on the individual array elements such as titles_array.first.author , etc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM