Добавить вычисляемый столбец в предложение выбора нескольких таблиц с помощью eager_load в Ruby on Rails Activerecord

У меня есть запрос с большим количеством объединений, и я eager_load с некоторыми ассоциациями в то время. И мне нужно вычислить некоторое значение как атрибут одной из моделей.

Итак, я пробую этот код:

ServiceObject
  .joins([{service_days: :ou}, :address])
  .eager_load(:address, :service_days)
  .where(ous: {id: OU.where(sector_code: 5)})
  .select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')

Где вызов функции SQL в select управляет данными из связанных таблиц addresses и ous.

Я получаю следующий SQL (поэтому мой столбец in_zone вычисляется и возвращается как первый столбец перед другими столбцами для всех моделей eager_load ed):

SELECT SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone, "SERVICE_OBJECTS"."ID" AS t0_r0, "SERVICE_OBJECTS"."TYPE" AS t0_r1, <omitted for brevity> AS t2_r36 FROM "SERVICE_OBJECTS" INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID" INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID" INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID" WHERE "OUS"."ID" IN (SELECT "OUS"."ID" FROM "OUS" WHERE "OUS"."SECTOR_CODE" = :a1)  [["sector_code", "5"]]

Но похоже, что in_zone недоступен из любой модели, используемой в запросе.

Мне нужно вычислить in_zone как атрибут объекта модели ServiceObject, как я могу это сделать?

Ruby on Rails 4.2.6, Ruby 2.3.0, oracle_enhanced adapter 1.6.7, Oracle 12.1

Ответ 1

Этот патч обезьяны помог @Envek:

module ActiveRecord
  Base.send :attr_accessor, :_row_

  module Associations
    class JoinDependency
      JoinBase && class JoinPart
        def instantiate_with_row(row, *args)
          instantiate_without_row(row, *args).tap { |i| i._row_ = row }
        end; alias_method_chain :instantiate, :row
      end
    end
  end
end

то можно сделать:

ServiceObject
  .joins([{service_days: :ou}, :address])
  .eager_load(:address, :service_days)
  .where(ous: {id: OU.where(sector_code: 5)})
  .select('SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')
  .first
  ._row_['in_zone']

Ответ 2

Я успешно реплицировал вашу проблему, и оказалось, что это известная проблема в Rails. Проблема в том, что при использовании eager_load Rails отображает столбцы всех загруженных таблиц в псевдонимы таблиц и столбцов в форме t0_r0, t0_r1 и т.д. (вы можете увидеть их в SQL, которые вы вставили в вопрос). И при этом он просто игнорирует пользовательские столбцы в select, возможно, потому, что он не может определить, какую загруженную таблицу должен привязать пользовательский столбец. Печально, что этот вопрос открыт уже более двух лет...

Тем не менее, я думаю, что нашел обходной путь. Кажется, что если вы не хотите загружать таблицы, но вручную присоединяете их (с помощью joins), вы можете также включить ихincludes) и пользовательские столбцы будут возвращены как не будет никакого алиасинга столбца. Дело в том, что вы не должны использовать ассоциации в предложениях joins, но вы должны сами указывать объединения. Также обратите внимание, что вы должны также указывать все столбцы из основной таблицы в ручном выборе (см. service_objects.* в выборе).

Попробуйте выполнить следующий подход:

ServiceObject
  .joins('INNER JOIN "SERVICE_DAYS" ON "SERVICE_DAYS"."SERVICE_OBJECT_ID" = "SERVICE_OBJECTS"."ID"')
  .joins('INNER JOIN "OUS" ON "OUS"."ID" = "SERVICE_DAYS"."OU_ID"')
  .joins('INNER JOIN "ADDRESSES" ON "ADDRESSES"."ID" = "SERVICE_OBJECTS"."ADDRESS_ID"')
  .includes(:service_days, :address)
  .where(ous: {id: OU.where(sector_code: 5)})
  .select('service_objects.*, SDO_CONTAINS(ous.service_area_shape, SDO_GEOMETRY(2001, 8307, sdo_point_type(addresses.lat, addresses.lng, NULL), NULL, NULL) ) AS in_zone')

Вычисление в select должно по-прежнему работать, поскольку связанные таблицы join ed вместе, но не должно присутствовать псевдонимы столбцов.

Конечно, этот подход означает, что вы получите три запроса вместо одного, но если вы не вернете огромное количество записей, следующие два запроса, выполняемые предложением includes, должны быть очень быстрыми, поскольку они просто загружают соответствующие записи с использованием внешних ключей.