为什么 Rails 中与 source_type 集的多态关联会导致错误的 SQL 语句?
Why does a polymorphic association in Rails with source_type set result in the wrong SQL statement?
我有三个模型:用户、组织和角色。一个用户可以通过一个角色访问多个组织,一个组织可以通过他们的角色拥有多个用户。另外,用户可以通过角色访问其他模型,所以角色模型有一个名为"access_to"的多态belongs_to关联,角色table有字段"user_id", "access_to_id" 和 "access_to_type" 来跟踪关联。这一切都很好,我可以使用 organisation.users 和 user.organisations 集合并取回预期的记录。
但是,已决定将组织模型重命名为 "Organization",使用美国英语而不是英国英语拼写(这是一个虚构的示例,实际问题类似,但额外的复杂性与这个问题)。该应用程序已 运行 多年,因此角色 table 中有数千条记录,其中 "access_to_type" 设置为 "Organisation"(带有 "s")。此外,为了遗留代码的目的,必须保留 "Organisation" 模型,而新代码使用 "Organization" 模型。
为了实现这一点,"source_type: 'Organisation'" 通过以下方式添加到 has_many:用户和新组织模型的关联,因此完整的代码如下所示:
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" 仍然按预期工作并且 return 使用此 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]]
并且在拼写为 "s" 的遗留模型上调用 "Organisation.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]]
然而,调用"Organization.first.users"不会return任何记录,查看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 既是 "Organization"(带有 "z")又是 "Organisation"(带有 "s").似乎设置 source_type: "Organisation" 在 access_to_type 上添加了一个附加条件,而不是替换 "Organization" 拼写为 "z" 的默认条件。
它还更改关联以查找 "organisations" table 而不是 "users" table。我希望它能简单地改变 "access_to_type" 条件。
为什么这对一个方向有效(为用户寻找组织),但对另一个方向(为组织寻找用户)无效?这是 Rails 中的错误(双重条件可能表明),还是我可以在关联配置中修复某些内容以使其工作? source_type 怎么可能在一个地方乱七八糟,而在另一个地方工作得很好?
(不幸的是,更改数据库中的 access_to_type 值不是一种选择,因为还有其他代码希望数据保持不变。)
这是在最小 Rails 6.0 应用程序中重现的问题:https://github.com/RSpace/polymorphic-issue
我找到了一个解决可疑错误的解决方案:有一个名为 polymorphic_name
的未记录方法,ActiveRecord 使用它来确定在进行多态查找时要使用的模型名称。
当我将组织模型更改为:
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
编辑:我现在明白为什么设置 source_type: "Organisation"
会在 organisations
table 而不是 users
table 中查找结果,因为 source_type
选项控制模型,table 和多态名称 as per the documentation。设置两次“access_to_type”仍然存在错误,但修复它不会让我的用例正常工作,因为 source_type
首先是控制关联的源类型.相反,我将寻求记录 polymorphic_name
方法,从而成为官方 ActiveRecord API.
的一部分
我有三个模型:用户、组织和角色。一个用户可以通过一个角色访问多个组织,一个组织可以通过他们的角色拥有多个用户。另外,用户可以通过角色访问其他模型,所以角色模型有一个名为"access_to"的多态belongs_to关联,角色table有字段"user_id", "access_to_id" 和 "access_to_type" 来跟踪关联。这一切都很好,我可以使用 organisation.users 和 user.organisations 集合并取回预期的记录。
但是,已决定将组织模型重命名为 "Organization",使用美国英语而不是英国英语拼写(这是一个虚构的示例,实际问题类似,但额外的复杂性与这个问题)。该应用程序已 运行 多年,因此角色 table 中有数千条记录,其中 "access_to_type" 设置为 "Organisation"(带有 "s")。此外,为了遗留代码的目的,必须保留 "Organisation" 模型,而新代码使用 "Organization" 模型。
为了实现这一点,"source_type: 'Organisation'" 通过以下方式添加到 has_many:用户和新组织模型的关联,因此完整的代码如下所示:
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" 仍然按预期工作并且 return 使用此 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]]
并且在拼写为 "s" 的遗留模型上调用 "Organisation.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]]
然而,调用"Organization.first.users"不会return任何记录,查看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 既是 "Organization"(带有 "z")又是 "Organisation"(带有 "s").似乎设置 source_type: "Organisation" 在 access_to_type 上添加了一个附加条件,而不是替换 "Organization" 拼写为 "z" 的默认条件。
它还更改关联以查找 "organisations" table 而不是 "users" table。我希望它能简单地改变 "access_to_type" 条件。
为什么这对一个方向有效(为用户寻找组织),但对另一个方向(为组织寻找用户)无效?这是 Rails 中的错误(双重条件可能表明),还是我可以在关联配置中修复某些内容以使其工作? source_type 怎么可能在一个地方乱七八糟,而在另一个地方工作得很好?
(不幸的是,更改数据库中的 access_to_type 值不是一种选择,因为还有其他代码希望数据保持不变。)
这是在最小 Rails 6.0 应用程序中重现的问题:https://github.com/RSpace/polymorphic-issue
我找到了一个解决可疑错误的解决方案:有一个名为 polymorphic_name
的未记录方法,ActiveRecord 使用它来确定在进行多态查找时要使用的模型名称。
当我将组织模型更改为:
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
编辑:我现在明白为什么设置 source_type: "Organisation"
会在 organisations
table 而不是 users
table 中查找结果,因为 source_type
选项控制模型,table 和多态名称 as per the documentation。设置两次“access_to_type”仍然存在错误,但修复它不会让我的用例正常工作,因为 source_type
首先是控制关联的源类型.相反,我将寻求记录 polymorphic_name
方法,从而成为官方 ActiveRecord API.