UUID和整数字段上的多态关联

时间:2015-07-13 23:34:37

标签: ruby-on-rails postgresql

给定具有integeruuid主键的表,集成多态连接(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

确保正确施法的最佳方法是什么?

6 个答案:

答案 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为整数
  • 任何第三方的字段

因此,从技术上讲,您无法在uuidid两个字段的范围内创建与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)
)