为什么 Rails 中与 source_type 设置的多态关联会导致错误的 SQL 语句?

Cas*_*ius 4 polymorphism activerecord ruby-on-rails polymorphic-associations

我有三个模型:用户、组织和角色。一个用户可以通过角色访问多个组织,一个组织可以通过角色拥有多个用户。此外,用户可以通过角色访问其他模型,因此角色模型有一个名为“access_to”的多态belongs_to关联,角色表有字段“user_id”、“access_to_id”和“access_to_type”来跟踪关联。这一切都很好,我可以使用organization.users和user.organizations集合并获取预期的记录。

但是,已决定将组织模型重命名为“Organization”,使用美国英语拼写而不是英国英语(这是一个虚构的示例,实际问题类似,但具有与此问题无关的额外复杂性)。该应用程序已运行多年,因此角色表中有数千条记录,其中“access_to_type”设置为“Organization”(带有“s”)。此外,“组织”模型必须保留以用于遗留代码,而“组织”模型则由新代码使用。

为了实现这一点,“source_type: 'Organization'” 通过以下方式添加到 has_many 中: User 和新的 Organization 模型的关联,因此完整的代码如下所示:

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
Run Code Online (Sandbox Code Playgroud)

调用“User.first.organizations”仍然按预期工作,并使用以下 SQL 语句返回预期记录:

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
Run Code Online (Sandbox Code Playgroud)

在用“s”拼写的遗留模型上调用“Organization.first.users”可以正常工作,生成预期的 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]]
Run Code Online (Sandbox Code Playgroud)

然而,调用“Organization.first.users”不会返回任何记录,当查看 Rails 生成的 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]]
Run Code Online (Sandbox Code Playgroud)

SQL 语句查找 access_to_type 为“Organization”(带有“z”)和“Organization”(带有“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

Cas*_*ius 6

我找到了一个解决可疑错误的解决方案:有一个名为polymorphic_nameActiveRecord 的未记录方法用于确定在进行多态查找时使用什么模型名称。

当我将组织模型更改为:

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
Run Code Online (Sandbox Code Playgroud)

然后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]]
Run Code Online (Sandbox Code Playgroud)

提交修复了我的示例:https://github.com/RSpace/polymorphic-issue/commit/648de2c4afe54a1e1dff767c7b980bb905e50bad

我仍然很想听听为什么另一种方法不起作用。这种解决方法似乎有风险,因为我只是通过挖掘 Rails 代码库发现了这种方法,并且它仅在内部使用:https://github.com/rails/rails/search ?q=polymorphic_name&unscoped_q=polymorphic_name

编辑:我现在明白为什么设置source_type: "Organisation"会导致在表中查找organisations而不是在users表中查找,因为该选项根据文档source_type控制模型、表和多态名称。两次设置“access_to_type”仍然存在一个错误,但是修复这个错误不会让我的用例正常工作,因为首先也是最重要的是控制关联的源类型。相反,我将追求将该方法记录下来,从而成为官方 ActiveRecord API 的一部分。source_typepolymorphic_name