简体   繁体   English

Rails 4-通过关联预加载has_many失败,并且穿透表的lambda条件

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

I have the following structure and am trying to include a has_many :through association. 我具有以下结构,并尝试include has_many :through关联。 It works properly if I don't preload the collections , but then I'm faced with an N+1 problem. 如果我不预加载collections ,它就可以正常工作,但是随后我遇到了N+1问题。

How can I pass conditions of the parent products association into the lambda when selecting collections ? 选择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

Works: 作品:

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

Doesn't work: 不起作用:

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

Throws error: 引发错误:

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" = $1 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):

Turns out I'm just an idiot. 原来我只是个白痴。 All I had to do is include(:products) in the lambda. 我要做的就是在lambda中include(:products) The working solution is below, and was further DRYed up by moving the logic for selecting collections through products into the named scope :by_active_products on Collections . 工作解决方案在下面,并且通过将用于通过products选择collections的逻辑移动到命名作用域:by_active_products on Collections来进一步干燥。 Both methods work with preloading and neither causes an N+1 issue. 两种方法都可以进行预加载,并且都不会导致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

Working now: 现在工作:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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