如何根据其他列是否有值而不是实际内容来验证模型中的唯一性?Scope似乎比较了第二列的内容。例如,我在我的用户模型列中有email和project_id(都是字符串(等。如果project_id为null或有任何值,我想验证电子邮件是否唯一。使用scope可以创建对象{email:"a@a.a"、project_id:nil}、{emael:"a@a.a"、project_id:"1"}、{email:"a@a.a"、project_id:"2"}等等。我想限制电子邮件的唯一性,以便前两个对象是可能的(project_id为nil或1,具有相同的电子邮件(,最后一个对象会抛出错误"电子邮件已经被占用",因为当project_id有值时,已经有用户具有相同的邮件。有没有合适的rails方法来实现这一点,或者我需要写一些自定义验证?
当然,我也有更好的电子邮件验证,不会接受"a@a.a",这只是一个例子:(
唯一性验证中的作用域选项与Rails作用域不同。它更多地与SQL相关,并且通常仅限于一个属性名称。
因此,由于不能在唯一性验证中使用自定义作用域,因此必须为此编写自定义验证。
validate :validate_email_on_project_id_existence
def validate_email_on_project_id_existence
if User.where(email: self.email).where(self.project_id.nil? ? 'project_id IS NULL' : 'project_id IS NOT NULL').first
errors.add(:email, "some error")
end
end
您可以创建自定义验证,并使用validates_with进行调用,在您的情况下,它将类似于:
class ExampleEmailUniqueness < ActiveModel::Validator
def validate(record)
error_message = "has already been taken"
if record.project_id.nil? && Example.where(project_id: nil, email: record.email).exists?
record.errors.add :email, error_message
elsif Example.where(email: record.email).where.not(project_id: nil).exists?
record.errors.add :email, error_message
end
end
end
class Example < ApplicationRecord
validates_with ExampleEmailUniqueness
end
根据您的场景进行验证将产生以下结果:
Example.new(email: 'a@a.a').save # Saved
Example.new(email: 'a@a.a', project_id: '1').save # Saved
Example.new(email: 'a@a.a', project_id: '2').save # Email has already been taken
Example.new(email: 'a@a.a').save # Email has already been taken
请注意,在保存一个非nil的模型后,保存一个具有nil project_id的模型仍然会通过验证并保存到数据库中,我不确定这是否是预期的行为。