Rails 4 - 通过关联预加载 has_many 失败,lambda 条件为通过 table

Rails 4 - Preloading has_many through association fails with lambda conditions of through table

我有以下结构,正在尝试 include 一个 has_many :through 关联。如果我不预加载 collections,它可以正常工作,但随后我会遇到 N+1 问题。

如何在选择 collections 时将父 products 关联的条件传递到 lambda 中?

class Collection < ActiveRecord::Base
  # == Schema Information
  #
  # Table name: products
  #  name                :string(255)      not null

  has_many    :products,
                inverse_of:     :collection

  has_many    :product_lines,
                through:        :products,
                inverse_of:     :products

  has_many    :lines,
                through:        :product_lines,
                inverse_of:     :product_lines
end


class Product < ActiveRecord::Base
  # == Schema Information
  #
  # Table name: products
  #  active              :boolean          default(TRUE), not null

  belongs_to  :collection,
                inverse_of:     :products

  has_many    :product_lines,
                inverse_of:     :product

  has_many    :lines,
                through:        :product_lines,
                inverse_of:     :product_lines
end


class ProductLine < ActiveRecord::Base
  belongs_to  :product,
                inverse_of:     :product_lines

  belongs_to  :line,
                inverse_of:     :product_lines
end


class Line < ActiveRecord::Base
  has_many    :product_lines,
                inverse_of:     :line

  has_many    :products,
                through:        :product_lines,
                inverse_of:     :product_lines

  # Gets Collections through Products where `product.active = true`
  # And orders the Collections by `collection.name`
  has_many    :collections,
                -> { where( products: {active: true} ).order(name: :ASC) },
                through:        :products,
                inverse_of:     :products

end

作品:

Line.all.each{ |line| line.collections }

无效:

Line.includes(:collections).all.each{ |line| line.collections }

抛出错误:

ActiveRecord::StatementInvalid - PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "products"
LINE 1: SELECT "collections".* FROM "collections" WHERE "products"."...
                                                        ^
: SELECT "collections".* FROM "collections" WHERE "products"."active" =  AND "collections"."id" IN (11, 30, 27, 12, 10, 13, 6, 4, 2, 7, 15, 9, 19, 1, 14, 8, 31, 5, 3, 29, 20, 17, 16, 37, 38, 41, 42, 43, 18, 44, 45, 26, 24, 25, 21, 22, 23):

原来我只是个白痴。我所要做的就是在 lambda 中 include(:products) 。工作解决方案在下面,并通过将选择 collectionsproducts 的逻辑移动到 Collections 上的命名范围 :by_active_products 中进一步干燥。这两种方法都适用于预加载,都不会导致 N+1 问题。

class Collection < ActiveRecord::Base
  # == Schema Information
  #
  # Table name: products
  #  name                :string(255)      not null

  has_many    :products,
                inverse_of:     :collection

  has_many    :product_lines,
                through:        :products,
                inverse_of:     :products

  has_many    :lines,
                through:        :product_lines,
                inverse_of:     :product_lines

  scope       :by_active_products, -> () { 
                includes(:products)
                .where({products: {active: true}})
                .order(name: :ASC)
                .uniq
              }
end


class Product < ActiveRecord::Base
  # == Schema Information
  #
  # Table name: products
  #  active              :boolean          default(TRUE), not null

  belongs_to  :collection,
                inverse_of:     :products

  has_many    :product_lines,
                inverse_of:     :product

  has_many    :lines,
                through:        :product_lines,
                inverse_of:     :product_lines
end


class ProductLine < ActiveRecord::Base
  belongs_to  :product,
                inverse_of:     :product_lines

  belongs_to  :line,
                inverse_of:     :product_lines
end


class Line < ActiveRecord::Base
  has_many    :product_lines,
                inverse_of:     :line

  has_many    :products,
                through:        :product_lines,
                inverse_of:     :product_lines

  # Gets Collections through Products where `product.active = true`
  # And orders the Collections by `collection.name`
  has_many    :collections,
                # Working using a named scope in the Collection model
                ->              { Collection.by_active_products },
                # Also working; Query params directly in the lambda
                # -> { includes(:products).where(products: {active: true}).order(name: :ASC).uniq },
                through:        :products,
                inverse_of:     :products

end

现在工作:

Line.includes(:collections).all.each{ |line| line.collections }