Ruby on Rails 中的 Delegated Types(委托类型)

Ruby on Rails 中的 Delegated Types(委托类型)

什么是 Delegated Types?

在 Rails 中,delegated types 是一种处理多态关联的高级方式,适用于需要一个父类(superclass)管理多个子类(subclasses)的情况。它的核心思想是:

  • 有一个父类模型(比如 Entry),用来存储所有子类共享的属性。
  • 每个子类模型(比如 MessageComment)继承这个父类,并拥有自己独立的表,存储子类特有的属性。
  • 通过 delegated_type 方法,父类可以动态地指向不同的子类对象。

这种方式比传统的单一表继承(STI)更灵活,因为它避免了将所有子类的属性挤在一个表中,导致不必要的字段冗余。


举个生活中的例子

想象一个博客系统:

  • 有两种内容类型:消息(Message) 和 评论(Comment)。
  • 它们都属于"条目(Entry)",共享一些属性(如创建时间),但也有各自独特的属性(比如消息有标题,评论有内容)。
  • 我们希望用一个统一的 Entry 来管理它们,但又不想混淆它们的特有数据。

第一步:设计数据模型

我们需要一个父类和多个子类,并为它们生成对应的数据库表。

1. 生成父类模型 Entry

Entry 是所有条目的超类,负责存储共享属性,同时记录它关联的子类类型和 ID。

运行以下命令生成 Entry 模型:

bash 复制代码
bin/rails generate model entry entryable_type:string entryable_id:integer

生成的表结构如下:

复制代码
entries
- id (integer)
- entryable_type (string)
- entryable_id (integer)
- created_at (datetime)
- updated_at (datetime)
  1. 生成子类模型 Message 和 Comment

生成 Message 模型:

bash 复制代码
bin/rails generate model message subject:string body:string

表结构:

复制代码
messages
- id (integer)
- subject (string)
- body (string)
- created_at (datetime)
- updated_at (datetime)

生成 Comment 模型:

bash 复制代码
bin/rails generate model comment content:string

表结构:

复制代码
comments
- id (integer)
- content (string)
- created_at (datetime)
- updated_at (datetime)
  1. 初步模型代码

运行完生成器后,模型文件如下:

ruby 复制代码
# app/models/entry.rb
class Entry < ApplicationRecord
end
ruby 复制代码
# app/models/entry.rb
class Message < ApplicationRecord
end
ruby 复制代码
# app/models/entry.rb
class Comment < ApplicationRecord
end

第二步:设置 Delegated Types

现在,我们需要告诉 Rails,Entry 是如何管理 Message 和 Comment 的。

  1. 在 Entry 中声明 delegated_type

在 Entry 模型中添加以下代码:

ruby 复制代码
class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[Message Comment], dependent: :destroy
end
  • delegated_type:Rails 提供的方法,用于设置委托类型。
  • :entryable:指定多态关联的名称(对应 entryable_type 和 entryable_id 字段)。
  • types: %wMessage Comment:列出所有可能的子类。
  • dependent: :destroy:当 Entry 被删除时,关联的子类记录也会被删除。

  1. 定义 Entryable 模块

子类需要知道它们属于某个 Entry,我们通过一个模块来实现这一点:

ruby 复制代码
# app/models/concerns/entryable.rb
module Entryable
  extend ActiveSupport::Concern

  included do
    has_one :entry, as: :entryable, touch: true
  end
end
  • has_one :entry, as: :entryable:表示子类有一个 Entry,并通过多态关联连接。
  • touch: true:当子类更新时,自动更新 Entry 的 updated_at 时间。

  1. 将模块应用到子类

修改 Message 和 Comment 模型:

ruby 复制代码
# app/models/message.rb
class Message < ApplicationRecord
  include Entryable
end
ruby 复制代码
# app/models/comment.rb
class Comment < ApplicationRecord
  include Entryable
end

第三步:测试基本功能

现在,Entry 可以管理 Message 和 Comment 了。我们来试试创建和查询对象。

  1. 创建对象

在 Rails 控制台中运行:

ruby 复制代码
# 创建一个 Message 类型的 Entry
Entry.create!(entryable: Message.new(subject: "Hello", body: "World"))

# 创建一个 Comment 类型的 Entry
Entry.create!(entryable: Comment.new(content: "Nice post!"))

数据库会记录:

复制代码
entries:
- id: 1, entryable_type: "Message", entryable_id: 1
- id: 2, entryable_type: "Comment", entryable_id: 1

messages:
- id: 1, subject: "Hello", body: "World"

comments:
- id: 1, content: "Nice post!"

  1. 查询对象
ruby 复制代码
# 获取所有 Entry
Entry.all
# => [#<Entry id: 1, entryable_type: "Message", entryable_id: 1>, ...]

# 获取第一个 Entry 的子类对象
entry = Entry.first
entry.entryable
# => #<Message id: 1, subject: "Hello", body: "World">

结论

通过 delegated types,我们成功地:

  • 让 Entry 统一管理 Message 和 Comment,但仍然保持子类的独立性。
  • 避免了 STI 可能导致的字段冗余问题。
  • 让 Entry 动态地指向不同的子类,提高了灵活性。

这种模式适用于任何需要共享父类逻辑但仍希望保持子类表独立的场景,比如:

  • 订单系统(Order 可以关联 PurchaseOrder 或 RefundOrder)。
  • 通知系统(Notification 可以关联 EmailNotification 或 SMSNotification)。
  • 活动系统(Event 可以关联 Webinar 或 Workshop)。
相关推荐
爱勇宝21 分钟前
从 Ctrl+CV 到 Enter:程序员正在失去什么
前端·后端·程序员
码事漫谈1 小时前
EdgeOne Makers + WorkBuddy:零基础也能快速搭建可上线的 AI 智能体(附图文教程)
后端
像我这样帅的人丶你还1 小时前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩1 小时前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构
烤代码的吐司君2 小时前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
苏三说技术2 小时前
为什么越来越多的人使用FastAPI?
后端
JavaGuide2 小时前
比 iTerm2 更适合 Claude Code/Codex 的终端,我换成 Ghostty 了
人工智能·后端
DyLatte2 小时前
AI 时代,最危险的不是被替代,而是努力不沉淀
前端·后端·程序员
神奇小汤圆3 小时前
架构师必备:CPU使用率不均匀排查
后端
神奇小汤圆3 小时前
Multi-Agent 执行闭环:AI Coding 真正进生产,要靠模型分工和工程护栏
后端