Rails 具有除非条件不需要的行为的唯一性验证

Rails uniqueness validation with unless condition unwanted behavior

我在具有属性标题和 is_deleted 的播放列表模型中进行了以下验证。 'is_deleted' 列(具有默认值 'false' 的 bool 类型列)的要点是因为我必须存档 播放列表不会删除它们。

validates :title, presence: true, uniqueness: true, unless: :is_deleted?

现在,如果我有一个记录,例如标题为 'Playlist 1' 和 is_deleted false 并尝试创建一个记录 is_deleted: true 它不会验证他,这是好的。如果我尝试将第一个记录的 is_deleted 列更新为 true,它不会验证,这也很好。
但现在我有两个标题为 "Playlist 1" 和 is_deleted 的播放列表:是的。如果我尝试将它们中的任何一个更新为 is_deleted: false,验证将不允许我这样做。它给出了 "Title has already been taken" 错误。我真的不明白为什么,因为我没有任何其他记录 is_deleted: false with the title: "Playlist 1".

这个 unless 在 ruby-land 运行。它按照锡盒上的说明进行操作:"validate uniqueness of title if post is not deleted"。请注意,它没有说 "validate uniqueness of title among only non-deleted records".

您应该使用数据库工具来实施此类限制。 Postgres partial unique indexes 可以做到这一点。

这是因为您的验证仅检查 :title 的唯一性。当它在将 is_deleted 更改为 false 后验证您的模型时,它会检查标题是否唯一。它会执行如下查询:

Playlist.where(title: 'Playlist 1').where.not(id: self.id).exists?

显然 已经有一个具有该标题的播放列表。

关键是它不会检查 other 播放列表上的 is_deleted 标志。它只是在当前播放列表上使用它来决定它是否应该验证。

您可能需要我们 validates_uniqueness_of 而不仅仅是 validates 这意味着您可以在验证中添加条件:

validates_uniqueness_of :title, 
  unless: :is_deleted?, 
  conditions: -> { where(is_deleted: false) }

这将确保您的唯一性检查仅针对同样未删除的其他播放列表进行检查。

您需要保留 validates :title, presence: true, unless: :is_deleted? 以验证标题是否存在(或者您可能会在 validates_uniqueness_of 上使用 allow_nilallow_blank 选项取得一些成功) .