Rails 3 如何对一个值进行唯一性验证并与列进行比较?

17

我有一个带有active列的模型,它是一个布尔类型。我想验证所有新增记录相对于company_id的唯一性,以便我可以添加尽可能多的记录到表中,只要设置activefalsecompany_id相同即可。每个company_id只应该有一条active记录。

我应该如何编写这个验证?我已经尝试过:

validates :company_id, :uniqueness => { :scope => :active }

但那似乎也会验证activefalse的唯一组合(这样我就永远不能在表中拥有超过两个带有相同active状态的company_id,无论active是什么)- 上述验证允许两条记录具有相同的company_id,其中一条为active = false,另一条为active = true。一旦这两条记录被添加,验证将阻止其他所有内容。

然后我尝试添加以下内容:

scope :active, where(:active => true)

但这似乎并没有改变验证(与上面相同的问题)。

我该如何编写此验证以便我可以添加许多记录,其中company_id相同,只要active为false,并且每个company_id只允许一个active = true

6个回答

15

无需使用validates_each - 这只是在您想要通过同一块传递多个属性时使用。只需创建自定义验证:

validate :company_id_when_active

def company_id_when_active
  if active? and CompanyTerm.exists? ["company_id = ? AND active = 1 AND id != ?", company_id, id.to_i]
    errors.add( :company_id, 'already has an active term')
  end
end

14

Rails 4+ 提供了一个更好的解决方案。

  validates_uniqueness_of :company_id, conditions: -> { where(active: true) }

查看文档


我们如何使用“validates”语法来编写这个? - Arslan Ali

4

好的,我认为我终于弄明白了。

查阅Rails指南后,我发现validates_each,这引导我找到了解决方案:

scope :active, where(:active => true)

validates_each :company do |model, attr, value|
  active = CompanyTerm.active.where(:company_id => value)
  model.errors.add(attr, 'already has an active term') unless active.empty?
end

我不确定这是否是编写此代码的最有效方式,但它能够正常工作。我非常乐意听取各种建议!


1

虽然这是一个老话题,但有最简单的解决方案。上面的答案几乎是正确的。您可以使用:if语句作为关键字,例如:

validates :active, :uniqueness => { :scope => :company_id }, :if => :active

但是你仍然可以使用update_all(:active => false)将所有内容分配为true - jdscosta91
3
这是错误的 - 它检查验证模型上活动值的价值,而不是所有其他记录上的值。 - koosa
@koosa:你能告诉我这个为什么是错的吗?如果验证模型处于非活动状态(active=false),那么在公司范围内不验证活动唯一性是可以的。 - Fumisky Wells
我也认为这是错误的...因为如果您有许多具有相同company_id且所有记录都为(active: false)的类似记录....然后您尝试添加一个新的相似记录,但这次它是(active: true),您将无法添加它...因为它将针对整个数据库检查唯一性,即使是(active: false)的记录,因此您将始终最终要么拥有许多相似的记录,全部都是非活动状态,要么只有一个活动状态....您永远不会同时拥有两者。 - ELTA

1

使用@raj的答案,使用validates方法:

validates(
    :company_id,
    uniqueness: { conditions: -> { where(active: true) } }
  )

-3
validates :company_id, :uniqueness => { :scope => :active } unless Proc.new { self.active }

也许你想直接联系公司本身

validates :company, :uniqueness => { :scope => :active } unless Proc.new { self.active }

编辑

根据您的解决方案,以下答案更加高效:

scope :active, where(:active => true)

validates_each :company do |model, attr, value|
  model.errors.add(attr, 'already has an active term') if CompanyTerm.active.exists?(:company_id => value)
end

这并不能使我的第一个测试用例通过,在该测试用例中,我尝试将一个新记录添加到一个空表中,并将active设置为true,然后验证另一个也将active设置为true的新记录是否有效(两者都具有相同的company_id)。实际上,似乎从未调用过该存储过程。 - neezer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接