重用has_one中的has_many关联

时间:2018-12-03 14:42:07

标签: ruby-on-rails

我有一个(简化的)数据模型,看起来像下面的样子。 例如。一个由“任务”使用的名为“目标”的键值表。

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

除非那行不通。

有什么想法可以实现我想要的吗?

1 个答案:

答案 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_idtasks外键列。

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)