rails急切地加载hasmany通过联接表



我的开发环境是在Rails 4.1和postgresql 上

我有三个模型与has_many直通关系:

class Item < ActiveRecord::Base
  has_many :item_parts
  has_many :parts, through: :item_parts
end
class Part < ActiveRecord::Base
  has_many :item_parts
  has_many :items, through: :item_parts
end
class ItemPart < ActiveRecord::Base
  belongs_to :item
  belongs_to :part 
end

item_parts联接表有一个unit_count属性,用于跟踪项包含的部分数。

当我在视图中遍历特定项的所有部分时,我还需要从联接表中获得unit_count值。

这给了我n+1查询问题。

我试着急切地加载联接表:

item.parts.includes(:item_parts)
ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)

但当我这样做之后:

part.item_parts.where(item_id: item.id).first.unit_count

我得到了以下每个部分的sql查询;急切的装载没有起作用

ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" = $1 AND "item_parts"."item_id" = $2  [["part_id", 24], ["item_id", 18]]

有什么建议吗?

在这个链接之后,我找到了我的案例的解决方案:Rails Eager Loading和where子句

我不知道活动记录查询方法:选择(http://apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/select)

我似乎无法急于加载联接表,因为在包含item_parts之后,我在项部分的迭代过程中使用where子句。

似乎where子句每次在迭代过程中使用时都会对数据库进行新的查询。

相反,select方法不会每次都命中数据库,而是对加载到内存中的活动记录集合有效。

所以我改了这个:

item.parts.includes(:item_parts)
part.item_parts.where(item_id: item.id).first.unit_count

这个:

item.parts.includes(:item_parts)
part.item_parts.select{|item_part| item_part.item_id == item.id}.first.unit_count

然后我有一个单独的查询来加载item_parts:

ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)

我想这是因为您使用的是;计数";方法,根据:

http://dev.mensfeld.pl/2014/09/activerecord-count-vs-length-vs-size-and-what-will-happen-if-you-use-it-the-way-you-shouldnt/

"在对象生命周期期间没有存储在内部,这意味着每次我们调用此方法时,都会再次执行SQL查询;

请尝试使用.length.size方法。

注:(引用文章)

length–collection.length

  • 返回集合的长度,而不执行其他查询…只要集合已加载
  • 当我们有懒惰加载的集合时,长度将加载整个集合进入内存,然后返回其长度
  • 如果使用不当,可能会占用你所有的内存
  • 当拥有一个急切加载的收藏时,速度真的很快

size–collection.size

  • 结合了前面两种方法的能力
  • 如果集合已加载,将对其元素进行计数(无其他查询)
  • 如果未加载集合,将执行附加查询

再看一遍,我不知道为什么要将计数添加到联接表中。

@items = Item.includes(:parts)
@items.each do |item|
   parts_count_per_item = item.parts.size
end

@parts = Part.includes(:items)
@parts.each do |part|
   items_count_per_part = part.items.size
end

最新更新