2

First of all, based on this (Rails association with multiple foreign keys) I figured out how to make two belong_to pointing to the same table.

I have something like that

class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
  belongs_to :co_author, inverse_of: :books, class_name: "Author"
end

class Author < ApplicationRecord
    has_many :books, ->(author) {
       unscope(:where).
       where("books.author_id = :author_id OR books.co_author_id = :author_id", author_id: author.id) 
    }
end

It's all good. I can do either

  • book.author
  • book.co_author
  • author.books

However, sometimes I need to eager load books for multiple authors (to avoid N queries).

I am trying to do something like:

Author.includes(books: :title).where(name: ["Lewis Carroll", "George Orwell"])

Rails 5 throws at me: "ArgumentError: The association scope 'books' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported."

I am trying to figure out what I should do?

Should I go with many-to-many association? It sounds like a solution. However, it looks like it will introduce it's own problems (I need "ordering", meaning that I need explicitly differentiate between main author and co-author).

Just trying to figure out whether I am missing some simpler solution...

Victor Ronin
  • 22,758
  • 18
  • 92
  • 184

2 Answers2

2

Why do you not use HABTM relation? For example:

# Author model
class Author < ApplicationRecord
  has_and_belongs_to_many :books, join_table: :books_authors
end

# Book model
class Book < ApplicationRecord
  has_and_belongs_to_many :authors, join_table: :books_authors
end

# Create books_authors table
class CreateBooksAuthorsTable < ActiveRecord::Migration
  def change
    create_table :books_authors do |t|
      t.references :book, index: true, foreign_key: true
      t.references :author, index: true, foreign_key: true
    end
  end
end

You can use eagerload like as following:

irb(main):007:0> Author.includes(:books).where(name: ["Lewis Carroll", "George Orwell"])

Author Load (0.1ms)  SELECT  "authors".* FROM "authors" WHERE "authors"."name" IN (?, ?) LIMIT ?  [["name", "Lewis Correll"], ["name", "George Orwell"], ["LIMIT", 11]]
HABTM_Books Load (0.1ms)  SELECT "books_authors".* FROM "books_authors" WHERE "books_authors"."author_id" IN (?, ?)  [["author_id", 1], ["author_id", 2]]
Book Load (0.1ms)  SELECT "books".* FROM "books" WHERE "books"."id" IN (?, ?)  [["id", 1], ["id", 2]]
Murat Baştaş
  • 156
  • 1
  • 8
  • First of all, you are right many-to-many is a better option for this. I started to move into that direction. However I decided to go with has_many :through (vs HABTM), because I need to have "ordering" (I need to know who is a main "author" and who is "co-author". It will require additional attributes on join table, which only supported in has_many :through. – Victor Ronin Jan 06 '18 at 18:19
  • Yes it's right, has_many :through is the better option for this case. – Murat Baştaş Jan 06 '18 at 20:44
0

Try this:

 Author.where(name: ["Lewis Carroll", "George Orwell"]).include(:books).select(:title)
Ritesh Ranjan
  • 1,012
  • 9
  • 16
  • It returns the same error: "ArgumentError: The association scope 'books' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported." – Victor Ronin Jan 03 '18 at 18:29