基于datetime属性存在性而非值的唯一性验证实现咨询
回答
不用非得写自定义验证器!Rails内置的唯一性验证其实就能通过conditions选项实现你要的逻辑,而且更简洁。
先说说你当前代码的问题:validates :promo_code, uniqueness: { scope: [:archived_at] }是把archived_at的具体值作为唯一性判断的一部分。这意味着如果两个已归档(archived_at非null)的记录刚好有相同的archived_at值,它们的promo_code会被判定为重复,这不符合你“只要archived_at存在就允许重复promo_code”的需求。
解决方案1:用内置验证的conditions选项
直接给唯一性验证加一个条件,只在archived_at为null时校验promo_code的唯一性:
validates :promo_code, uniqueness: { conditions: -> { where(archived_at: nil) }, message: "must be unique when not archived" # 可选:自定义错误提示 }
这个逻辑完全匹配你的需求:
- 当
archived_at为null时,promo_code必须唯一(符合你期望失败的场景); - 当
archived_at非null时,不管它的具体值是什么,promo_code都可以重复(符合你期望通过的场景)。
解决方案2:自定义验证器(如果需要更复杂逻辑)
如果你之后要扩展验证逻辑,或者想把这个规则封装成可复用的验证器,可以写一个自定义验证器:
# app/validators/promo_code_uniqueness_validator.rb class PromoCodeUniquenessValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) # 只有当未归档时才校验唯一性 return if record.archived_at.present? # 检查是否存在其他未归档的同promo_code记录 duplicate = record.class.where(promo_code: value, archived_at: nil) duplicate = duplicate.where.not(id: record.id) if record.persisted? # 排除自身(更新场景) if duplicate.exists? record.errors.add(attribute, :taken, message: options[:message] || "must be unique when not archived") end end end # 在你的模型里使用 validates :promo_code, promo_code_uniqueness: true
总结
如果只是满足你当前的需求,用内置的conditions选项就足够了,不需要自定义验证器。只有当你需要更灵活的逻辑时,再考虑自定义验证器。
内容的提问来源于stack exchange,提问作者JBlake
相关产品推荐
相关产品推荐

