ActiveRecord嵌套关联

问题描述 投票:0回答:3

我有一些模型,如ServicePaymentManager

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-rails ruby activerecord rails-activerecord
3个回答
0
投票

您可以定义一个额外的has_many通过关系,它可以为您提供所需的内容。

has_many:经理,通过::付款

class Service < ApplicationRecord
  validates :name, presence: true

  has_and_belongs_to_many :tickets
  has_many :payments

  has_many :managers, through: :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

0
投票

我不明白你的需要%100但是我想出来并且想法如下。请检讨一下。

payments = Payment.joins(:service).select(:service_id, :manager_id, :value).
    group_by(&:service_id)

managers = Manager.where('id IN(SELECT manager_id FROM payments)').
    select(:name,:id)
    group_by(&:id).transform_values{ |value| value.first }


result = []
ResultClass = Struct.new(:service, :manager, :payment_value)
Service.all.each do |service|
  payment = payments[service.id]
  manager = managers[payment.manager_id]
  result << ResultClass.new(service, manager, payment.value)
end

有了这段代码,我们实际上正在尝试在引擎盖下完成rails includes机制。我们在这里做了什么?

  • 我们得到付款并将它们转换为散列,即id是散列密钥
  • 然后我们得到那些付款的经理,我们还将这些记录转换为经理ID作为散列密钥
  • 我们用ResultClass创建了Struct来存储像servicemanager这样的对象的引用
  • 我们开始迭代。由于我们将paymentsmembers作为哈希,因此快速查找并且没有时间复杂性。总体而言,此迭代的时间复杂度为O(n)

如果您有任何疑问,请随时发表评论。


0
投票

经过多次尝试,我终于找到了一些我做过的拼写错误并且我想要使用这样的代码

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或代码优化提案有其他意见?

© www.soinside.com 2019 - 2024. All rights reserved.