如何验证联表模型中Attendee对同一Open House的注册唯一性
问题本质
你把校验的放置层级搞错了。“同一参会人不能重复注册同一场开放日”是两个实体交叉关系的约束,承载这个交叉关系的唯一模型就是Registration联表,根本没必要把逻辑硬塞到OpenHouse或者Attendee的单表校验里。
落地方案
这个约束要分两层实现,缺一不可:
- 应用层校验直接写在Registration模型里,用框架自带的唯一性校验能力,指定校验的匹配范围
这段逻辑会在保存Registration记录前,检查当前提交的# app/models/registration.rb class Registration < ApplicationRecord belongs_to :open_house belongs_to :attendee # 校验open_house_id与attendee_id的组合不允许重复 validates :open_house_id, uniqueness: { scope: :attendee_id, message: "你已提交过本场开放日的注册申请" } endopen_house_id + attendee_id组合是否已经存在,存在就直接返回校验错误,阻断重复记录写入。 - 数据库层补充对应联合唯一索引做兜底
应用层的唯一性校验在并发请求场景下会出现竞态问题(比如两个参数完全相同的请求同时到达,都通过了校验后写入重复数据),必须靠数据库约束从根上堵上漏洞:
生成对应的数据库迁移,添加联合唯一索引:
有了这个索引,哪怕应用层校验出现疏漏,数据库也会直接拒绝重复的组合写入,从底层保证数据一致性。# 迁移文件核心代码 add_index :registrations, [:open_house_id, :attendee_id], unique: true
为什么不适合把校验放在OpenHouse模型上
- OpenHouse模型本身只需要维护单表属性(比如举办时间、场地、参与容量),它天生拿不到单次注册请求对应的attendee上下文,硬要在这里加校验的话,就得在保存逻辑里手动跨表查询Registration记录,完全把联表业务的耦合度拉满。
- 后续如果调整注册规则(比如允许同一参会人取消注册后重新报名、允许同一参会人带2个同行人完成注册),所有修改都得改动OpenHouse的模型逻辑,维护成本远高于把约束收敛在Registration中间表的设计。
内容的提问来源于stack exchange,提问作者Seth
相关产品推荐
相关产品推荐

