Rails是否应采用Polymorphic关联?三表关联方案咨询
表关联设计疑问:Injury、Sport、Exercise的关联方案选择
我有三张表:Injury、Sport、Exercise,正在纠结如何建立它们之间的关联。需求是能在Sport和Exercise表中关联展示对应的Injury记录,而且未来可能扩展更多关联表,所以考虑使用Polymorphic relation(多态关联)。但我理解这种方式会为Sport和Exercise分别创建重复的Injury记录,比如:
Injury_id : 1 name = acl tear injurable_id = 1 type = Sport Injury_id: 2 name = acl tear id = 1 type = Exercise
我曾想过能不能让单条Injury记录同时关联Sport和Exercise,不确定是否可行:
Injury_id : 1 name = acl tear injurable_id = 1 injurable_type = Sport and Exercise
另一种方案是在Sport和Exercise表中分别添加Injury_ids字段存储关联ID,但担心后续会有查询问题。我知道多态关联可以避免在关联表中添加Injury_id字段。
目前我构思了两种实现方式:
方案一:多态关联
# Injury 模型 class Injury < ApplicationRecord belongs_to :injurable, polymorphic: true end # Sport 模型 class Sport < ApplicationRecord has_many :injuries, as: :injurable end # Exercise 模型 class Exercise < ApplicationRecord has_many :injuries, as: :injurable end
查询示例:
Sports控制器:
sport = Sport.includes(:injuries).find(1)
Exercises控制器:
exercise = Exercise.includes(:injuries).find(1)
方案二:单独存储关联ID
# Injuries表结构示例 Injury_id: 1, 2, 3... # Sports表新增字段 injuries_id: 1, 2 (存储多个Injury ID,用逗号分隔) # Exercises表新增字段 injuries_id: 1, 3 (存储多个Injury ID,用逗号分隔)
查询示例:
Sports控制器:
sport = Sport.find(1) injuries = sport.pluck(:injuries_id) injury_names = Injury.find(injuries).pluck(:name)
Exercises控制器:
exercise = Exercise.find(1) injuries = exercise.pluck(:injuries_id) injury_names = Injury.find(injuries).pluck(:name)
请问我应该选择哪种方案,或者有没有其他更优的实现方式?
直接排除方案二的原因
方案二中用逗号分隔存储多个ID是典型的数据库设计反模式,会引发一系列问题:
- 查询效率极低:无法利用数据库索引,每次查询都要手动拆分字符串,还容易出现类型转换错误
- 数据一致性难保障:更新关联时需手动修改字符串,极易出现重复ID、格式混乱等问题
- 无法使用ActiveRecord原生关联能力:不能用
includes、joins等优化查询的方法,只能手动处理,代码冗余且易出错 - 扩展性极差:未来新增关联表时,必须重复添加类似字段,维护成本飙升
纠正对多态关联的误解
你之前对多态关联的理解有误——普通一对多多态关联确实会让一条Injury只属于一个对象,但这不是多态关联的唯一形态。如果想让单条Injury同时关联Sport、Exercise甚至未来的其他表,应该使用多对多多态关联,通过中间表维护关联关系,完全不需要重复创建Injury记录。
最优方案:多对多多态关联
数据库表设计
- 保留
injury表:存储伤病基础信息(如name) - 创建中间表
injurables(符合Rails命名惯例),字段包括:injury_id:关联Injury表的外键injurable_id:关联Sport/Exercise等表的IDinjurable_type:关联对象的类型(如'Sport'、'Exercise')
模型关联代码
# Injury 模型 class Injury < ApplicationRecord has_many :injurables, dependent: :destroy has_many :sports, through: :injurables, source: :injurable, source_type: 'Sport' has_many :exercises, through: :injurables, source: :injurable, source_type: 'Exercise' # 未来新增表时,只需添加类似的has_many through关联即可 end # Sport 模型 class Sport < ApplicationRecord has_many :injurables, as: :injurable, dependent: :destroy has_many :injuries, through: :injurables end # Exercise 模型 class Exercise < ApplicationRecord has_many :injurables, as: :injurable, dependent: :destroy has_many :injuries, through: :injurables end # Injurable 中间模型 class Injurable < ApplicationRecord belongs_to :injury belongs_to :injurable, polymorphic: true end
操作示例
- 给Sport关联已有的Injury:
sport = Sport.find(1) injury = Injury.find_by(name: 'acl tear') sport.injuries << injury
- 查询Sport的所有关联伤病:
sport = Sport.includes(:injuries).find(1) sport.injuries # 直接获取关联的Injury记录集合
- 查询某条Injury关联的所有Sport和Exercise:
injury = Injury.find(1) injury.sports # 获取关联的Sport集合 injury.exercises # 获取关联的Exercise集合
方案优势
- 无数据重复:单条Injury记录可关联多个不同类型的对象,无需重复创建相同伤病记录
- 扩展性极强:未来新增关联表(如
Workout)时,仅需添加对应模型关联,无需修改现有表结构 - 符合数据库规范:中间表维护多对多关系,支持索引,查询效率高,数据一致性有保障
- 原生支持ActiveRecord能力:可使用
includes、joins、where等所有关联查询方法,代码简洁易维护
总结
- 直接排除方案二,逗号存储多ID的方式完全不可取
- 若需求是“一个Sport/Exercise拥有多个专属Injury记录”,普通一对多多态关联(方案一)可行;但从你的需求(未来扩展、单条Injury关联多类型)来看,多对多多态关联是最优选择
内容的提问来源于stack exchange,提问作者Chrismisballs
相关产品推荐
相关产品推荐

