Почему ассоциация polymorphi c в Rails с установленным source_type приводит к неверному выражению SQL? - PullRequest
4 голосов
/ 24 января 2020

У меня есть три модели: пользователь, организация и роль. У пользователя есть доступ ко многим организациям через роль, и у организации может быть много пользователей через их роли. Кроме того, пользователи могут иметь доступ к другим моделям через роли, поэтому модель ролей имеет полиморфную c принадлежность_ к ассоциации с именем "access_to", а таблица ролей имеет поля "user_id", "access_to_id" и "access_to_type" для отслеживания ассоциации. Все это прекрасно работает, я могу использовать коллекции organisation.users и user.organisations и вернуть ожидаемые записи.

Тем не менее, было решено переименовать модель Организации в «Организация», используя вместо английского «Английский» sh на английском языке Engli *1031* (это вымышленный пример, реальная проблема похожа, но с дополнительной сложностью, не относящейся к этому вопросу). Приложение работает в течение многих лет, поэтому в таблице ролей находятся тысячи записей, для которых «access_to_type» установлен в «Organization» (с «s»). Кроме того, модель «Организация» должна храниться для устаревшего кода, в то время как модель «Организация» используется новым кодом.

Для достижения этой цели к исходному тексту добавляется «source_type: 'Organization» ». has_many through: ассоциации для пользователя и новой модели организации, поэтому полный код выглядит следующим образом:

class Role < ApplicationRecord
  belongs_to :user
  belongs_to :access_to, polymorphic: true
end

class User < ApplicationRecord
  has_many :roles, autosave: true, foreign_key: "user_id"

  has_many(
    :organizations,
    through: :roles,
    source: :access_to,
    source_type: "Organisation"
  )
end

class Organization < ApplicationRecord
  self.table_name = 'organisations'

  has_many :roles, as: :access_to
  has_many :users, through: :roles, source: :access_to, source_type: "Organisation"
end

class Organisation < ApplicationRecord
  has_many :roles, as: :access_to
  has_many :users, through: :roles
end

Вызов «User.first.organizations» по-прежнему работает, как ожидается, и возвращает ожидаемые записи с этим SQL оператор:

SELECT "organisations".* FROM "organisations" 
INNER JOIN "roles" ON "organisations"."id" = "roles"."access_to_id"
WHERE "roles"."user_id" = ? AND "roles"."access_to_type" = ?
LIMIT ?  [["user_id", 1], ["access_to_type", "Organisation"], ["LIMIT", 11]]

И вызов «Organisation.first.users» для устаревшей модели, записанный с «s», работает нормально, генерируя ожидаемое SQL:

SELECT "users".* FROM "users" INNER JOIN "roles"
ON "users"."id" = "roles"."user_id"
WHERE "roles"."access_to_id" = ?
AND "roles"."access_to_type" = ?
LIMIT ? 
[["access_to_id", 1],
["access_to_type", "Organisation"],
["LIMIT", 11]]

Однако вызов «Organization.first.users» не возвращает никаких записей, и причина очевидна, если посмотреть на оператор SQL, который Rails генерирует:

SELECT "organisations".* FROM "organisations"
INNER JOIN "roles" ON "organisations"."id" = "roles"."access_to_id"
WHERE "roles"."access_to_id" = ?
AND "roles"."access_to_type" = ?
AND "roles"."access_to_type" = ?
LIMIT ? 
[["access_to_id", 1],
["access_to_type", "Organization"], 
["access_to_type", "Organisation"],
["LIMIT", 11]]

Оператор SQL ищет записи ролей. где access_to_type - это и «Организация» (с «z»), и «Организация» (с «s»). Кажется, что установка source_type: «Organization» добавляет дополнительное условие для access_to_type вместо замены условия по умолчанию, где «Organization» пишется с «z».

Также это меняет ассоциацию, чтобы посмотреть в «» таблица организаций вместо таблицы пользователей. Я бы ожидал, что он просто изменит условие «access_to_type».

Почему это работает в одном направлении (поиск организаций для пользователя), а не в другом направлении (поиск пользователей для организации)? Это ошибка в Rails (двойное условие может указывать на это), или я могу что-то исправить в конфигурации ассоциации, чтобы она работала? Как source_type может так сильно портить в одном месте и нормально работать в другом?

(Изменение значений access_to_type в базе данных, к сожалению, не вариант, так как есть другой код, ожидающий, что данные останутся неизменными. )

Вот проблема, воспроизводимая в минимальном приложении Rails 6.0: https://github.com/RSpace/polymorphic-issue

1 Ответ

1 голос
/ 27 января 2020

Я нашел решение, которое работает с предполагаемой ошибкой: существует недокументированный метод с именем polymorphic_name, который ActiveRecord использует, чтобы определить, какое имя модели использовать при выполнении поиска polymorphi c.

Когда я изменяю модель организации:

class Organization < ApplicationRecord
  self.table_name = 'organisations'

  has_many :roles, as: :access_to
  has_many :users, through: :roles

  def self.polymorphic_name
    "Organisation"
  end
end

, затем Organization.first.users генерирует SQL Я хочу:

SELECT "users".* FROM "users" INNER JOIN "roles"
ON "users"."id" = "roles"."user_id"
WHERE "roles"."access_to_id" = ?
AND "roles"."access_to_type" = ?
LIMIT ?  [
["access_to_id", 1],
["access_to_type", "Organisation"],
["LIMIT", 11]]

Фиксация, исправившая мой пример: https://github.com/RSpace/polymorphic-issue/commit/648de2c4afe54a1e1dff767c7b980bb905e50bad

Я все еще хотел бы услышать, почему другой подход не работает, хотя. Этот обходной путь кажется рискованным, поскольку я просто обнаружил этот метод, копаясь в базе кода Rails, и он используется только внутри: https://github.com/rails/rails/search?q=polymorphic_name&unscoped_q=polymorphic_name

...