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: %w[Message 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)。
相关推荐
程序员爱钓鱼1 小时前
Python编程实战 · 基础入门篇 | Python的缩进与代码块
后端·python
earthzhang20214 小时前
第3讲:Go垃圾回收机制与性能优化
开发语言·jvm·数据结构·后端·性能优化·golang
thinktik6 小时前
AWS EKS 集成Load Balancer Controller 对外暴露互联网可访问API [AWS 中国宁夏区]
后端·kubernetes·aws
追逐时光者6 小时前
将 EasySQLite 解决方案文件格式从 .sln 升级为更简洁的 .slnx
后端·.net
驰羽7 小时前
[GO]GORM 常用 Tag 速查手册
开发语言·后端·golang
AntBlack7 小时前
虽迟但到 :盘一盘 SpringAI 现在发展得怎么样了?
后端·spring·openai
ss2738 小时前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
舒一笑9 小时前
🚀 PandaCoder 2.0.0 - ES DSL Monitor & SQL Monitor 震撼发布!
后端·ai编程·intellij idea
Java中文社群9 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
helloworddm10 小时前
Orleans 流系统握手机制时序图
后端·c#