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)。
相关推荐
小信啊啊15 分钟前
Go语言切片slice
开发语言·后端·golang
Victor3562 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易2 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧2 小时前
Range循环和切片
前端·后端·学习·golang
WizLC2 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3562 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法2 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长3 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Python编程学习圈4 小时前
Asciinema - 终端日志记录神器,开发者的福音
后端
bing.shao4 小时前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang