避免在具有不同关系的 Rails STI 上进行 n+1 查询

Posted

技术标签:

【中文标题】避免在具有不同关系的 Rails STI 上进行 n+1 查询【英文标题】:Avoiding n+1 query on rails STI with different relationships 【发布时间】:2018-03-13 14:19:58 【问题描述】:

假设我有以下模型:

class Building < ActiveRecord::Base
  has_many :rooms
  has_many :training_rooms, class_name: 'TrainginRoom', source: rooms
  has_many :computers, through: :training_rooms
end

class Computer < ActiveRecord::Base
  belongs_to :room
end

class Room < ActiveRecord::Base
  belongs_to :building
end

class Office < Room
end

class TrainingRoom < Room
  has_many :computers
end

假设我遵循 jsonapi 规范并使用包含的***成员在单个 http 调用中呈现每个相关对象。

所以建筑物/表演看起来有点像这样:

json.data do
  json.id building.id
  json.type building.type
  json.attributes do
    # render the attributes
  end
  json.relationships do
    # render the relationships
  end
end

json.included.do
  json.array! building.rooms.each do |room|
    json.type room.type
    json.id  room.id
    json.attributes do
     # render the attribtues
    end

    json.relationships do |room|
      # render included member's relationships
      # N+1 Be here
    end
  end
end

我无法从包含的成员中急切地加载关系,因为它并不存在于数组的所有成员中。

例如,在控制器中:

@building = Building.eager_load(
   rooms: :computers 
).find(params[:id])

如果房间关系中有办公室,则不会工作,因为它没有计算机关系。

@building = Building.eager_load(
  :rooms,
  traning_rooms: :computers
).find(params[:id])

也不起作用,因为训练室关系提供了对要侧载的计算机的访问权限,但不能在渲染代码中直接访问,因此是无用的缓存。

此外,我尝试将默认范围应用于培训室以预先加载计算机关联,但这也没有达到预期的效果。

此时我唯一能想到的是将计算机关系应用于 Room 类,但我并不想这样做,因为只有培训室才应该有计算机。

我很想听听任何想法。

【问题讨论】:

source: rooms 应该是source: :rooms 请修正错字:has_many :training_rooms, class_name: 'TrainingRoom', source: :rooms 【参考方案1】:

由于计算机模型中没有training_room_id,因此您必须在定义关系时明确提及foreign_key

class TrainingRoom < Room
  has_many :computers, foreign_key: :room_id
end

现在您将能够预先加载记录:

@building = Building.eager_load( training_rooms: :computers ).where(id: params[:id])

希望对你有帮助!

【讨论】:

以上是关于避免在具有不同关系的 Rails STI 上进行 n+1 查询的主要内容,如果未能解决你的问题,请参考以下文章

用抽象类在Rails中表示has_many关系

在 Rails 中创建一个与 STI 一起使用的动态子类

单表继承(STI)问题

Rails STI 和视图重用

Rails 5:在连接表记录上保存记录时的 STI 空键

处理 Rails 中 STI 子类的路由的最佳实践