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

时间:2017-10-24 19:47:16

标签: ruby-on-rails ruby-on-rails-4 has-many-through

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

如何在选择products时将父collections关联的条件传递给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" = $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):

1 个答案:

答案 0 :(得分:0)

原来我只是个白痴。我所要做的就是在lambda中include(:products)。下面是工作解决方案,并通过将collectionsproducts选择的逻辑移动到:by_active_products上的命名范围Collections进一步进行了干预。这两种方法都适用于预加载,并且都不会导致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 }