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)。
相关推荐
到账一个亿39 分钟前
后端树形结构
后端
武子康42 分钟前
大数据-31 ZooKeeper 内部原理 Leader选举 ZAB协议
大数据·后端·zookeeper
我是哪吒43 分钟前
分布式微服务系统架构第155集:JavaPlus技术文档平台日更-Java线程池实现原理
后端·面试·github
代码老y1 小时前
Spring Boot + 本地部署大模型实现:安全性与可靠性保障
spring boot·后端·bootstrap
LaoZhangAI1 小时前
OpenAI API 账号分层完全指南:2025年最新Tier系统、速率限制与升级攻略
前端·后端
红衣信1 小时前
前端与后端存储全解析:从 Cookie 到缓存策略
前端·后端·面试
Kyrie_Li1 小时前
(十五)Spring Test
java·后端·spring
WildBlue1 小时前
🎉 手写call的魔法冒险:前端开发者的“换身份”指南🚀
前端·后端
fortmin1 小时前
使用Apache Pdfbox生成pdf
后端
weixin_437398212 小时前
转Go学习笔记
linux·服务器·开发语言·后端·架构·golang