rails: delegated_type 委托类型(一对多的场景)

在 Rails 7 中引入了 delegated_type 方法,这个方法提供了一种新的多态关联方式,简化了不同模型之间的多态关系管理。它用于实现“代表类型(delegated_type)”模式,让一个表(代表)通过代理访问不同的关联对象,而不用像传统的 polymorphic 关联那样处理单个表中的所有逻辑。

  • Article 有多个 Comment (1:N)
  • Photo 有多个 Comment (1:N)

01 示例

假设我们希望 Comment 模型可以关联到 ArticlePhoto,并且可以通过 comment.commentable 来直接访问具体的对象。

ER图

02 创建 Comment 模型

首先,我们用 rails g model 命令生成 Comment 模型。

$ Could not generate field 'commentable_id' with unknown type 'bigint'.

# 这个是公用的 delegate 模型
rails g model Comment content:text commentable_type:string commentable_id:integer

这里我们定义了 commentable_typecommentable_id,因为 delegated_type 依赖这两个字段来确定实际的关联模型。生成并迁移数据库:

rails db:migrate

03 创建 Article 和 Photo 模型

接下来,创建两个可以拥有 Comment 的模型:ArticlePhoto

rails g model Article title:string content:text
rails g model Photo title:string url:string

生成迁移并迁移数据库:

rails db:migrate

04 设置 Comment 的 delegated_type

Comment 模型中,使用 delegated_type 定义多态关系,使得 Comment 可以代理到 ArticlePhoto

打开 app/models/comment.rb,添加 delegated_type 配置:

# app/models/comment.rb
class Comment < ApplicationRecord
  delegated_type :commentable, types: %w[Article Photo], dependent: :destroy

  # 这里可以添加其他方法或验证
end

通过 delegated_type :commentable,我们将 Comment 配置为一个代表类型,其中 commentable 可以是 ArticlePhoto

05 设置 Article 和 Photo 的关联

ArticlePhoto 模型中,我们分别添加与 Comment 的关联关系。

# app/models/article.rb
class Article < ApplicationRecord
  has_many :comments, as: :commentable
end

# app/models/photo.rb
class Photo < ApplicationRecord
  has_many :comments, as: :commentable
end

通过 as: :commentableArticlePhoto 表示它们都是可以拥有 Comment 的对象。

06 使用示例

完成以上配置后,我们可以通过以下方式创建和访问 Comment 的多态关联。

创建 Comment 实例

比如我们创建一个 Article 和一个 Photo,然后为它们分别添加评论:

# 创建 Article 和 Photo 实例
article = Article.create(title: "An Interesting Article", content: "Content of the article")
photo = Photo.create(title: "A Beautiful Photo", url: "photo.jpg")

# 创建 Comment 实例,分别属于 article 和 photo
comment1 = Comment.create(content: "Nice article!", commentable: article)
comment2 = Comment.create(content: "Amazing photo!", commentable: photo)

访问 Comment 的关联对象

可以直接通过 commentable 访问 Comment 所属的对象,无论是 Article 还是 Photo

comment1.commentable # => #<Article id: 1, title: "An Interesting Article", content: "Content of the article">
comment2.commentable # => #<Photo id: 1, title: "A Beautiful Photo", url: "photo.jpg">

这样,Comment 可以代理到不同的类型,并且我们可以通过 commentable 直接访问它属于的对象。

总结

使用 delegated_type 定义了一个简单而清晰的多态关联,使 Comment 可以属于 ArticlePhoto。这使得代码结构更加模块化,且容易扩展。如果以后想增加其他类型,只需在 Comment 模型中添加到 types 数组即可。

07 delegated_type 和 polymorphic 的区别

特性delegated_typepolymorphic
适用关系一对多多态关系一对多和多对多多态关系
关系的类型集固定、有限、稳定的类型集灵活、可以随时扩展
使用场景代理单一属主类型,简化多态逻辑多态关联较多、类型集多样的关系
维护成本当新增类型时需修改代码扩展性强,不需修改代码

delegated_type 是一种非常有用的工具,但适用范围有限。它在特定的多态场景中比 polymorphic 更简洁,但无法完全取代 polymorphic。如果是复杂或灵活需求较高的多态场景,传统的 polymorphic 关联依然更合适。

rails delegated_type