ActiveRecord:确保只有一条记录具有特定的属性值?

时间:2011-01-20 16:09:42

标签: validation ruby-on-rails-3 activerecord

我有一个种族表,在各种状态下有很多种族。但我需要确保只有一个种族被标记为 current = true 。以下是我在Race模型验证中使用的内容。

# current: boolean
validate :only_one_current

private
def only_one_current
  if self.current && (Race.current_race.id != self.id)
    errors.add(:base, "Races can have only one current race")
  end
end

这似乎在大部分时间都有效,但偶尔也没有,我不知道为什么。如果它不起作用,它就会在删除当前不同的记录后不允许使用 current = t 保存新记录。我认为这与AR的持久性有关。

必须有更好的方法来做到这一点吗?

2 个答案:

答案 0 :(得分:5)

您的问题实际上超出了ActiveRecord。无论你如何实现before_save方法,总是有可能发生竞争条件(没有双关语),并且两条记录在数据库中具有current = true。有关详细信息,请参阅Concurrancy and Integrity section for validates_uniqueness_of

核心问题是检查记录是否具有current = true并且操作将记录设置为current = true的逻辑不是原子的。这个问题经常出现在并发系统中。

要解决此问题,您需要在数据库中使用unique key index。我建议您将当前标志更改为优先级字段。优先级是一个具有唯一键索引的整数。数据库将保证不会同时存在具有相同优先级值的两条记录。 “当前”竞赛将永远是具有最高优先级值的竞赛。

竞争条件实际上仍然存在 - 你现在只有一种方法来检测它。当您将争用设置为当前(通过查询表中的最大优先级值)时,如果另一个记录当前保持与您尝试保存的记录具有相同的优先级值,则会生成异常。只需捕获重复的密钥异常,然后重试。

答案 1 :(得分:0)

您需要将其称为before_save,而不是验证器:

before_save :only_one_current