Я работаю над приложением 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 какие-либо передовые методы или шаблоны проектирования, позволяющие справиться с таким сценарием без ущерба для удобства обслуживания и производительности?
Будем очень признательны за любые идеи или предложения о том, как решить эту проблему!
На самом деле вы не можете этого сделать. Внешний ключ связи в Rails одинаков для всех строк. Можете ли вы даже представить себе, насколько сумасшедшим было бы присоединение, предварительная загрузка или нетерпеливая загрузка ассоциаций в противном случае? Я думаю, вам нужно подойти к проблеме, разработав схему по-другому.
Это невозможно и по довольно веской причине.
В 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 отдела продаж) или даже промежуточной модели, в которой хранится внешний идентификатор.
Спасибо вам огромное за такое. понятное объяснение :)
А как насчет введения промежуточной модели, на которую вы можете ссылаться из своего отчета и которая содержит внешний UUID?