给定具有integer
和uuid
主键的表,集成多态连接(has_many
)的最佳方法是什么?例如:
class Interest < ActiveRecord::Base
# id is an integer
has_many :likes, as: :likeable
end
class Post < ActiveRecord::Base
# id is a UUID
has_many :likes, as: :likeable
end
class User < ActiveRecord::Base
has_many :likes
has_many :posts, through: :likes, source: :likeable, source_type: "Post"
has_many :interests, through: :likes, source: :likeable, source_type: "Interest"
end
class Like < ActiveRecord::Base
# likeable_id and likeable_type are strings
belongs_to :likeable, polymorphic: true
belongs_to :user
end
许多查询都有效:
interest.likes
post.likes
user.likes
然而:
user.interests
给出:
PG :: UndefinedFunction:ERROR:运算符不存在:整数=字符变化 第1行:......兴趣&#34; INNER JOIN&#34;喜欢&#34; ON&#34;兴趣&#34;。&#34; id&#34; =&#34;喜欢&#34; .... ^ 提示:没有运算符匹配给定的名称和参数类型。您可能需要添加显式类型转换。 :SELECT&#34;兴趣&#34;。* FROM&#34;兴趣&#34; INNER JOIN&#34;喜欢&#34; ON&#34;兴趣&#34;。&#34; id&#34; =&#34;喜欢&#34;。&#34; likeable_id&#34;在哪里&#34;喜欢&#34;。&#34; user_id&#34; = $ 1 AND&#34;喜欢&#34;。&#34; likeable_type&#34; = $ 2
确保正确施法的最佳方法是什么?
答案 0 :(得分:2)
这是一个古老的问题,但这是我的建议。
这更多是架构问题。不要将UUID ID和整数ID结合使用,它会很快变得凌乱。如果可以,请将整数ID迁移到UUID或将uuid还原为整数ID。
我的经验是,最好的解决方案可能是使用相当不错的友好ID宝石:https://github.com/norman/friendly_id
在将来这种情况下会中断的情况下,基本上它只是一种生成/管理网络的工具,该工具将使用这种路由路径:posts/this-is-a-potential-slug
而不是posts/1
,但是什么也没有阻止您使用posts/<UUID here>
或posts/<alphanumeric string here>
。
通常,如果您使用的是UUID,那是因为您不想显示连续的整数。友好ID可以很好地避免该问题。
答案 1 :(得分:1)
无法使用Rails指定必需的强制转换。相反,在演员表中添加一个生成的列,并声明一个额外的belongs_to关联以使用它。例如,在迁移中使用以下方法:
add_column :interests, :_id_s, 'TEXT GENERATED ALWAYS AS (id::text) STORED'
add_index :interests, :_id_s
这在您的模型中:
class Like
belongs_to :_likeable_cast, polymorphic: true, primary_key: :_id_s, foreign_key: :likeable_id, foreign_type: :likeable_type
class User
has_many :interests, through: :likes, source: :_likeable_cast, source_type: "Interest"
然后user.interests
通过替代关联加入,即使用生成的列进行强制转换。
我建议对likeable_id列使用文本的列类型而不是varchar,以避免在连接期间进行不必要的转换并确保使用索引。
答案 2 :(得分:0)
你能描述一下你的likes
牌桌吗?我想它包含
user_id
为整数,likeable_id
为整数,likeable_type
为整数因此,从技术上讲,您无法在uuid
和id
两个字段的范围内创建与likeable_id
字符串和likeable_type
相同的多态关联作为整数。
作为解决方案 - 您只需添加id
作为posts
表的主键,而不是uuid
。如果您可能不希望在URL中显示帖子的ID,或出于其他安全原因 - 您仍然可以像以前一样使用uuid
。
答案 3 :(得分:0)
您也许可以定义自己的方法来在您的likes
模型中检索Interest
。
def likes
Like.where("likeable_type = ? AND likeable_id = ?::text", self.class.name, id)
end
此解决方案的问题在于您没有定义关联,因此“ has_many through”之类的东西将无法工作,您还必须自行定义这些方法/查询。
答案 4 :(得分:0)
您是否考虑过像在关联宏中强制转换外键或主键一样的事情?例如。 has_many :likes, foreign_key: "id::UUID"
或类似名称。
答案 5 :(得分:0)
在 Rails 6.1.4 上测试
使用 likeable_id 作为字符串效果很好,Rails 负责 ID 的转换。
这是我的代码示例
将多态“所有者”添加到时间线事件模型的迁移
class AddOwnerToTimelineEvent < ActiveRecord::Migration[6.1]
def change
add_column :timeline_events, :owner_type, :string, null: true
add_column :timeline_events, :owner_id, :string, null: true
end
end
多态模型
class TimelineEvent < ApplicationRecord
belongs_to :owner, polymorphic: true
end
现在我们有 2 个所有者,其 id 为 Bigint 的 Contact 和 id 为 uuid 的 Company,您可以在 SQL 中看到 rails 已经将它们转换为字符串
contact.timeline_events
TimelineEvent Load (5.8ms) SELECT "timeline_events"."id", "timeline_events"."at_time",
"timeline_events"."created_at", "timeline_events"."updated_at",
"timeline_events"."owner_type", "timeline_events"."owner_id" FROM
"timeline_events" WHERE "timeline_events"."owner_id" = $1 AND
"timeline_events"."owner_type" = $2 [["owner_id", "1"],
["owner_type", "Contact"]]
company.timeline_events
TimelineEvent Load (1.3ms) SELECT "timeline_events"."id", "timeline_events"."action",
"timeline_events"."at_time", "timeline_events"."created_at",
"timeline_events"."updated_at", "timeline_events"."owner_type",
"timeline_events"."owner_id" FROM "timeline_events" WHERE
"timeline_events"."owner_id" = $1 AND "timeline_events"."owner_type" =
$2 [["owner_id", "0b967b7c-8b15-4560-adac-17a6970a4274"],
["owner_type", "Company"]]
但是当您为特定所有者类型加载时间轴事件时有一个警告,而 rails 无法为您进行类型转换 必须自己做铸造。例如所有者是公司的加载时间表
TimelineEvent.where(
"(owner_type = 'Company' AND uuid(owner_id) in (:companies))",
companies: Company.select(:id)
)