Обработка условных ассоциаций `belongs_to` с различными типами внешних ключей в Rails

Я работаю над приложением Rails, в котором мне нужно управлять полиморфной ассоциацией с различными типами внешних ключей на основе атрибута reporter_type. Основная проблема заключается в том, что типы внешних ключей различаются (integer для reporter_id и string для partition_reporter_id), и мне нужно условно переключаться между этими ключами в пределах одной модели.

Я использую рельсы 7.0.8.4

Текущая настройка В моей модели Report у меня есть полиморфная ассоциация, определенная следующим образом:

class Report < ApplicationRecord

  belongs_to :report_configuration, counter_cache: true
  belongs_to :reporter, polymorphic: true, optional: true

  Other associations and validations...
end

reporter_id обычно соответствует customer_id (целому числу), но мне нужно ввести partition_reporter_id для обработки строкового UUID для идентификаторов Salesforce.

Требования Если reporter_type равен 'partition', внешний ключ должен быть partition_reporter_id. Для всех остальных значений reporter_type внешний ключ должен быть reporter_id.

Проблемы Стандартные полиморфные ассоциации Rails предполагают один и тот же тип данных для внешнего ключа в разных типах связанных записей. Использование двух разных типов данных усложняет логику модели и SQL-запросы, которые строит Rails. Rails изначально не поддерживает условную логику непосредственно в определениях ассоциаций.

Вопросы Как я могу изменить или расширить ассоциацию belongs_to :reporter, чтобы условно использовать разные внешние ключи (partition_reporter_id или reporter_id) на основе reporter_type? Существуют ли в Rails какие-либо передовые методы или шаблоны проектирования, позволяющие справиться с таким сценарием без ущерба для удобства обслуживания и производительности? Будем очень признательны за любые идеи или предложения о том, как решить эту проблему!

🤔 А знаете ли вы, что...
Ruby on Rails также известен как Rails или RoR.


51
1

Ответ:

Решено

Это невозможно и по довольно веской причине.

В ActiveRecord все ассоциации (даже полиморфные) имеют фиксированный столбец внешнего ключа.

Когда вы присоединяетесь через ассоциацию, Rails создает следующий SQL:

SELECT "reports".* FROM "reports" 
INNER JOIN "reporters" 
ON "reports"."id" = "reporter"."report_id"

Если вы хотите выполнить соединение, где внешний столбец является динамическим, возможно, это можно сделать с помощью оператора Case:

SELECT "reports".* FROM "reports" 
INNER JOIN "reporters"
  ON CASE
  WHEN reports.reporter_type = 'partition' THEN
    reports.partition_reporter_id = reporters.some_column
  ELSE 
    reports.id = reporters.rapporter_id

Это не кажется таким уж плохим, пока вы не вспомните, что ActiveRecord должен быть многоязычным и поддерживать широкий спектр систем управления реляционными базами данных, а в Rails есть множество методов для объединения и быстрой загрузки. Если вы затем добавите к этому немного полиморфизма, станет еще хуже.

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

Лучшее решение — просто переосмыслить это и рассмотреть возможность использования как суррогатного ключа (автоинкрементного целого числа), так и внешнего идентификатора (uuid отдела продаж) или даже промежуточной модели, в которой хранится внешний идентификатор.