Ruby on rails 验证有条件时数据库中的唯一性验证

Ruby on rails 验证有条件时数据库中的唯一性验证,ruby-on-rails,postgresql,indexing,unique-index,Ruby On Rails,Postgresql,Indexing,Unique Index,当存在多个进程时,在Rails中使用唯一性验证是不安全的,除非约束也在数据库上实施(在我的例子中是PostgreSQL数据库,请参阅) 在我的例子中,唯一性验证是有条件的:只有当模型中的另一个属性变为true时,才应该执行唯一性验证。所以我有 class Model < ActiveRecord::Base validates_uniqueness_of :text, if: :is_published? def is_published? self.is_publi

当存在多个进程时,在Rails中使用唯一性验证是不安全的,除非约束也在数据库上实施(在我的例子中是PostgreSQL数据库,请参阅)

在我的例子中,唯一性验证是有条件的:只有当模型中的另一个属性变为true时,才应该执行唯一性验证。所以我有

class Model < ActiveRecord::Base
  validates_uniqueness_of   :text, if: :is_published?

  def is_published?
    self.is_published
  end
end
类模型
因此,该模型有两个属性:
is_published
(一个布尔值)和
text
(一个文本属性)<代码>文本
在所有
模型
类型的模型中应是唯一的,如果
已发布
为真

使用唯一索引(如链接的博客文章中所建议的)太过约束,因为无论发布的
值是多少,它都会强制执行约束

有人知道PostgreSQL数据库上的“条件”索引吗?或者另一种方法来解决这个问题?

是的,请使用

示例:

我认为-给定的速度不是您主要关心的问题-您可以在不创建额外db索引的情况下实现适当的唯一性验证。目标可以在应用程序级别实现。如果您想要条件唯一性,这尤其有价值,因为某些数据库(例如MySQL<8的版本)不支持部分索引(或所谓的过滤索引)

我的解决方案基于以下假设:

  • 唯一性检查(验证器)由Rails在与依赖它的保存/销毁操作相同的事务中运行
这一假设似乎是正确的:

#save和#destroy都包装在一个事务中,确保您在验证或回调中所做的任何事情都将在它的保护范围内发生

事务调用可以嵌套。默认情况下,这会使嵌套事务块中的所有数据库语句成为父事务的一部分

这样,您就可以使用悲观锁定()专门锁定要在验证器中评估唯一性的记录。这将阻止另一个同时运行的验证器——以及它之后发生的任何事情——执行,直到事务结束时释放锁。这确保了验证存储对的原子性和正确的唯一性实施

在您的代码中,它看起来是这样的:

class Model < ActiveRecord::Base
  validates :text, uniqueness: {
    conditions: ->{ lock.where(is_published: true) }
  }
end
类模型{lock.where(is_published:true)}
}
结束

我能看到的唯一缺点是在整个验证保存过程中锁定了db记录。这在重载情况下不会很好地工作,但是许多应用程序在这种情况下无论如何都不能工作。

另请参见Rails文档中的
add\u index
class Model < ActiveRecord::Base
  validates :text, uniqueness: {
    conditions: ->{ lock.where(is_published: true) }
  }
end