You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带双作用域的唯一性验证未按预期工作问题排查

问题描述

我有一个GroupItem模型,配置了唯一性验证以确保同一记录仅能加入一个分组,但设置group_id和groupable_type为作用域后,创建不同分组下的同一关联记录时验证仍失败。具体代码及测试过程如下:

# A member of a Group
class GroupItem < ApplicationRecord
  belongs_to :group
  belongs_to :groupable, polymorphic: true, optional: true

  # You can only be a member of a group once
  validates_uniqueness_of_without_deleted :groupable_id, uniqueness: { scope: %i[group_id groupable_type] }
end

当创建两个独立分组并尝试将同一Client记录加入两个分组时,第二个GroupItem的验证失败,尽管两个记录的group_id不同:

client = Client.create
group1 = Group.create
group2 = Group.create

group_item_1 = GroupItem.create(group: group1, groupable: client)
group_item_2 = GroupItem.new(group: group2, groupable: client)

> group_item_1
#<GroupItem:0x000000012ac86a60> {
                :id => 6,
    :groupable_type => "Client",
      :groupable_id => 7,
          :group_id => 7,
        :deleted_at => nil,
        :created_at => Fri, 07 Jun 2024 16:03:29.705078000 EDT -04:00,
        :updated_at => Fri, 07 Jun 2024 16:03:29.705078000 EDT -04:00
}

> group_item_2
#<GroupItem:0x000000011f77db00> {
                :id => nil,
    :groupable_type => "Client",
      :groupable_id => 7,
          :group_id => 8,
        :deleted_at => nil,
        :created_at => nil,
        :updated_at => nil
}

> group_item_2.valid?
false

> group_item_2.errors
#<ActiveModel::Errors:0x000000012aae5fd0 @base=#<GroupItem id: nil, groupable_type: "Client", groupable_id: 7, group_id: 8, deleted_at: nil, created_at: nil, updated_at: nil>, @errors=[#<ActiveModel::Error attribute=groupable_id, type=taken, options={:uniqueness=>{:scope=>[:group_id, :groupable_type]}, :value=>7}>]>

请问我的唯一性验证配置哪里出错了?


解决方案

你的问题出在validates_uniqueness_of_without_deleted的参数结构错误:你把scope选项嵌套在了uniqueness哈希中,但这个方法(通常来自paranoia这类软删除gem)的参数规则和Rails原生validates_uniqueness_of一致,需要将scope作为顶层选项直接传入。

修正后的模型代码:

class GroupItem < ApplicationRecord
  belongs_to :group
  belongs_to :groupable, polymorphic: true, optional: true

  # You can only be a member of a group once
  validates_uniqueness_of_without_deleted :groupable_id, scope: %i[group_id groupable_type]
end

补充说明

仅靠模型验证无法完全避免并发场景下的竞态条件(比如两个请求同时创建同一分组下的同一关联记录),因此必须配合数据库唯一索引来保障数据一致性。创建对应的迁移文件:

# db/migrate/XXXXXX_add_unique_index_to_group_items.rb
class AddUniqueIndexToGroupItems < ActiveRecord::Migration[7.0]
  def change
    add_index :group_items, [:groupable_id, :groupable_type, :group_id], 
              unique: true, 
              where: "deleted_at IS NULL"
  end
end

这个索引会忽略已软删除的记录,同时确保同一groupable记录在同一分组下只能存在一条有效记录。


内容的提问来源于stack exchange,提问作者Jeremy Thomas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 16:46:19