TL; DR:
app / models / badge.rb
class Badge < ApplicationRecord
has_many :reactions
has_many :reaction_target_comments, through: :reactions, source: :reaction_target, source_type: 'Comment'
has_many :reaction_target_projects, through: :reactions, source: :reaction_target, source_type: 'Project '
# ... etc
end
Объяснение
Насколько я знаю, для полиморфных многих-в-ко-многим, вы не можете выполнить
class Badge < ApplicationRecord
has_many :reaction_targets, through: :reactions
# ...
end
... (возникнет ошибка, как вы уже заметили), потому что, если это возможно, это означает, что выполнение badge.reaction_targets
должно вернуть массивэкземпляров "другой модели", то есть:
badge = Badge.find(1)
puts badge.reaction_targets.to_a
# => [<Comment id: 1>,
# <Project id: 45>,
# <SomeModel id: 99>,
# <COmment id: 3>,
# ...]
^ Приведенный выше код выглядит действительно интуитивно верно, не так ли?... потому что он должен возвращать массив записей различного типа, что совершенно логично, потому что это полиморфные отношения, верно?Да, это совершенно верно, но становится проблематичным в отношении того, какую строку SQL следует генерировать.См. Пример SQL-эквивалента ниже:
puts badge.reaction_targets
# => SELECT "WHAT_TABLE_1".* FROM "WHAT_TABLE_1" INNER JOIN reactions ...
# SELECT "WHAT_TABLE_2".* FROM "WHAT_TABLE_2" INNER JOIN reactions ...
# SELECT "WHAT_TABLE_3".* FROM "WHAT_TABLE_3" INNER JOIN reactions ...
# ... etc
^ ..., поскольку ожидается, что reaction_targets
будет иметь экземпляры с различными моделями, тогда представьте, какой SQL-запрос необходим для получения всех записей?Хотя, я думаю, что можно получить их все, но это, вероятно, будет не прямой оператор SQL, а, вероятно, имеющий комбинацию некоторой логики Ruby-кода на стороне приложения.Кроме того, тип возвращаемого значения должен быть не ActiveRecord::Associations::CollectionProxy
, а, вероятно, новым типом объекта, специально предназначенного для удовлетворения этих has_many
полиморфных отношений.Я имею в виду, представьте, если вы сделаете что-то, как показано ниже:
badge.reaction_targets.where(is_enabled: true, first_name: 'Hello')
# or even more complex:
badge.reaction_targets.joins(:users).where(users: { email: 'email@example.com' }).
^ ... Я не думаю, что есть прямое SQL-выражение, которое выше для полиморфных объединений, и поэтому, возможно, поэтомувам необходимо добавить source
и source_type
для разных «моделей», как и мой ответ выше.
Возможное решение
Если вы нормально возвращаете объект Array
вместообычного ActiveRecord::Associations::CollectionProxy
объекта вы можете сделать что-то вроде ниже.(Хотя вам все равно придется указывать каждое полиморфное has_many
отношение и, возможно, добавить еще в будущем.)
class Badge < ApplicationRecord
has_many :reactions
has_many :reaction_target_comments, through: :reactions, source: :reaction_target, source_type: 'Comment'
has_many :reaction_target_projects, through: :reactions, source: :reaction_target, source_type: 'Project'
def reaction_targets
reaction_target_comments.to_a + reaction_target_projects.to_a
end
end
Пример использования:
badge = Badge.find(1)
puts badge.reaction_targets
# =>[<Comment id: 1>,
# <Comment id: 45>,
# <Project id: 99>,
# <Project id: 3>,
# ...]