【发布时间】:2014-02-18 20:37:07
【问题描述】:
问题
我有以下 ActiveRecord 模型:
class Person
belongs_to :favourite_car, class_name: 'Car'
belongs_to :business_car, class_name: 'Car'
belongs_to :home_car, class_name: 'Car'
end
当我想访问所有这三个关联时,它会生成三个选择查询:
SELECT * FROM cars WHERE cars.id = ?
这本质上是 N+1 问题。
理想情况下,我希望它只生成一个表单查询
SELECT * FROM cars WHERE cars.id IN (?, ?, ?)
可能的解决方案
我可以将其移至 has_many :through => :join_table 关联,并在连接表中使用一列指示关联类型,然后使用 includes([:join_table, :cars]) 急切加载关联。但是,在这种情况下,它仅将 3 个查询减少到 2 个,并引入了一个额外的表。
另一种可能的解决方案
另一种可能的解决方案是手动加载关联,如下所示:
module EagerLoader
def eager_load(*associations)
reflections = associations.map { |association| self.class.reflections[association.to_sym] }
raise 'Not all are valid associations' if reflections.any?(&:nil?)
reflections.group_by { |association| association.klass }.each do |klass, reflections|
load_associations(klass, reflections)
end
self
end
private
def load_associations(klass, reflections)
primary_key = klass.primary_key
ids = reflections.map { |reflection| public_send(reflection.foreign_key) }
records = klass.where(id: ids)
reflections.each_with_index do |reflection, i|
record = records.find do |record|
record.public_send(primary_key) == ids[i]
end
public_send("#{reflection.name}=", record)
end
end
end
我已经测试过了,它可以工作。
class Person
include EagerLoader
end
Person.find(2).eager_load(:favorite_car, :business_car, :home_car)
但是,当您想做类似的事情时,这仍然对您没有帮助
Person.includes(:favourite_car, :business_car, :home_car)
例如,在人员索引页面上。这将查询的数量从 3N+1 减少到 4,但实际上只需要 2 个。
有没有更好的办法解决这个问题?
【问题讨论】:
-
最喜欢的汽车与商务车有何不同(即在模型意义上)?当您访问 Person 的汽车数据时,您通常需要所有三个位(最喜欢的、商务和家用汽车)吗?
-
favourite_car、business_car 和 home_car 都是 Car 的实例,所以模型没有区别。几乎所有时间,我都需要访问所有三个位或根本不访问。
-
抱歉...没问得那么好。最喜欢的汽车与家用汽车的区别是什么(是否有区分这些模型的“布尔”或“类型”列)?换句话说,你怎么知道汽车是最喜欢的、企业还是家? (我真的在问一个理由......不要烦人:))。
-
有没有理由一个人没有拥有_one而不是belongs_to?似乎这将是一个更容易设置。你的表结构是什么样的?
-
@craig.kaminsky 根据 belongs_to 关联的 activerecord 约定,people 表中有 favourite_car_id、business_car_id 和 home_car_id 外键。
标签: ruby-on-rails ruby rails-activerecord eager-loading