ActiveRecord вложенные ассоциации - PullRequest
0 голосов
/ 24 февраля 2019

У меня есть некоторые модели, такие как Service, Payment и Manager

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 может иметь несколько Payment с, и каждая 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]линия.

Ответы [ 3 ]

0 голосов
/ 25 февраля 2019

Я не понял твоей потребности% 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

С помощью этого кода мы на самом деле пытаемся создать механизм рельсов includes под капотом.Что мы на самом деле здесь сделали?

  • Мы получаем платежи и конвертируем их в хеш, в котором идентификатор является хеш-ключом
  • Затем мы получаем менеджеров, у которых есть платежи, и мы также конвертируем эти записис идентификатором менеджера в качестве хеш-ключа
  • Мы создали ResultClass с Struct для хранения ссылок на объекты, такие как service и manager
  • Мы начинаем итерацию.Из-за того, что у нас есть payments и members в качестве хэша, быстрый поиск и отсутствие временной сложности.В целом, сложность времени этой итерации составляет O (n)

Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь оставлять комментарии.

0 голосов
/ 25 февраля 2019

После многих попыток я, наконец, нашел несколько ошибок, которые я совершил, и заархивировал поведение. Я хочу использовать такой код

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 или предложений по оптимизации кода?

0 голосов
/ 24 февраля 2019

Вы можете определить дополнительные отношения 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
...