我有一个(简化的)数据模型,看起来像下面的样子。 例如。一个由“任务”使用的名为“目标”的键值表。
class Target < ActiveRecord::Base
belongs_to :task
end
class Task < ActiveRecord::Base
has_one :name_target, -> { where(name: "name") },
class_name: "Target"
has_one :version_target, -> { where(name: "version") },
class_name: "Target"
end
出于性能方面的考虑,我希望避免针对每种关系提出单独的要求。 当前,以下代码将触发3个单独的SQL请求:
t = Task.find(1) # SELECT * FROM tasks WHERE id = $1
t.name_target.value # SELECT * FROM targets WHERE task_id = $1 AND name = "name"
t.version_target.value # SELECT * FROM targets WHERE task_id = $1 AND name = "version"
我想做的是预加载所有目标,然后让AR将其重用于has_one
关系。因此,将查询数量从3个减少到2个。
类似这样的东西:
class Task < ActiveRecord::Base
has_many :targets
has_one :name_target, -> { where(name: "name") },
class_name: "Target"
has_one :version_target, -> { where(name: "version") },
class_name: "Target"
end
t = Task.includes(:targets).find(1) # SELECT * FROM tasks WHERE id = $1
# SELECT * FROM targets WHERE task_id = $1
t.name_target.value # use preload
t.version_target.value # use preload
除非那行不通。
有什么想法可以实现我想要的吗?
答案 0 :(得分:1)
我想做的是预加载所有目标,然后重用AR 对于has_one关系。因此,减少查询数量 从3到2。
对于关联,您实际上无法做到这一点,因为ActiveRecord没有以这种方式彼此链接的不同关联的概念。您可以使用enumerable来迭代已加载的关联:
Task.eager_load(:targets).find(1).targets.detect { |t| t.name == "name" }
但是您从一开始就以错误的方式解决问题。要创建一对一关联,您需要向name_target_id
表中添加version_target_id
和tasks
外键列。
class AddTargetsToTasks < ActiveRecord::Migration[5.2]
def change
add_reference :tasks, :name_target, foreign_key: { to_table: :targets }
add_reference :tasks, :version_target, foreign_key: { to_table: :targets }
end
end
并使用belongs_to
。
class Task < ActiveRecord::Base
has_many :targets
belongs_to :name_target, class_name: "Target"
belongs_to :version_target, class_name: "Target"
end
然后,您需要加载每个关联,以避免进一步查询:
Task.eager_load(:targets, :name_target, :version_target)