首页 > 解决方案 > ActiveRecord 嵌套关联

问题描述

我有一些模型,Service例如PaymentManager

class Service < ApplicationRecord
  validates :name, presence: true

  has_and_belongs_to_many :tickets
  has_many :payments
end

class Payment < ApplicationRecord
  belongs_to :manager
  belongs_to :service
  validates :value, :manager_id, :service_id, presence: true
end

class Manager < ApplicationRecord
  validates :name, presence: true
end

Service可以有多个Payments 并且每个Payment都有一个Manager.

我想使用所有嵌套关联作为哈希来获取整个数据,我可以重新格式化(或映射)并发送给客户端。我被查询困住了:

Service.includes(payments: :manager).references(:payments, :managers)

因为它正在使用延迟加载,所以我需要执行以下操作:

services.first.payments.first.manager

获取数据,这是非最优的。

是否可以使用所有嵌套关联获取所有数据?

我做了这样的计算:

services = Service.includes(payments: :manager)
                   .references(:payments, :managers)

  result = []

  services.each do |service|
    service.payments.each do |payment|
      manager_name = payment[:manager][:name]
      value = payment[:value]

      service[manager_name] = value
    end

    result.push(service)
  end

NoMethodError (undefined method '[]' for nil:NilClass): 并在线出错manager_name = payment[:manager][:name]

标签: ruby-on-railsrubyactiverecordrails-activerecord

解决方案


经过多次尝试,我终于找到了一些我制作的拼写错误并归档了我想要使用此类代码的行为

services = Service.eager_load(payments: :manager)

  services.reduce([]) do |acc, service|
    service_rec = {
      id: service[:id],
      name: service[:name],
      surgery: service[:surgery]
    }

    service.payments.each do |payment|
      manager_name = payment.manager[:name]
      value = payment[:value]

      service_rec[manager_name] = value
    end

    acc.push(service_rec)
  end

和它产生的 SQL 查询

SQL (0.2ms)  SELECT "services"."id" AS t0_r0, "services"."code" AS t0_r1, "services"."name" AS t0_r2, "services"."surgery" AS t0_r3, "services"."enabled" AS t0_r4, "services"."created_at" AS t0_r5, "services"."updated_at" AS t0_r6, "payments"."id" AS t1_r0, "payments"."service_id" AS t1_r1, "payments"."manager_id" AS t1_r2, "payments"."value" AS t1_r3, "payments"."created_at" AS t1_r4, "payments"."updated_at" AS t1_r5, "managers"."id" AS t2_r0, "managers"."name" AS t2_r1, "managers"."enabled" AS t2_r2, "managers"."created_at" AS t2_r3, "managers"."updated_at" AS t2_r4 FROM "services" LEFT OUTER JOIN "payments" ON "payments"."service_id" = "services"."id" LEFT OUTER JOIN "managers" ON "managers"."id" = "payments"."manager_id"

有趣的是,使用includes而不是eager_load产生三个查询

Service Load (0.1ms)  SELECT "services".* FROM "services"
  ↳ app/services/payments_service.rb:6
  Payment Load (0.1ms)  SELECT "payments".* FROM "payments" WHERE "payments"."service_id" IN (?, ?, ?, ?, ?, ?, ?, ?)  [["service_id", 1], ["service_id", 2], ["service_id", 3], ["service_id", 4], ["service_id", 5], ["service_id", 6], ["service_id", 7], ["service_id", 8]]
  ↳ app/services/payments_service.rb:6
  Manager Load (0.1ms)  SELECT "managers".* FROM "managers" WHERE "managers"."id" IN (?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4]]

我们也可以使用Service.includes(payments: :manager).references(:payments, :managers)并获得相同的查询,eager_load但输入时间更长))

感谢大家的参与!有人对eager_load代码优化建议有其他意见吗?


推荐阅读