ruby-on-rails – 如何确定我的ActiveRecord对象是否违反了唯一的数据库密钥/索引?

ActiveRecord的validates_uniqueness_of是 vulnerable to race conditions.要真正确保唯一性,需要额外的保护措施. ActiveRecord RDocs的一个建议是在数据库上创建一个唯一索引,例如通过包含在您的迁移中:
add_index :recipes,:name,:unique => true

这将确保在数据库级别名称是唯一的.但是这种方法的一个缺点是,在尝试保存副本时返回的ActiveRecord :: StatementInvalid异常不是很有用.在捕获此异常时,无法确定错误是由重复记录生成的,而不仅仅是由损坏的SQL生成的.

正如RDocs所建议的那样,一种解决方案是解析异常附带的消息,并尝试检测诸如“重复”或“唯一”之类的字,但这很麻烦,而且消息是特定于数据库后端的.对于SqlLite3,我的理解是该消息是完全通用的,并且不能以这种方式解析.

鉴于这是ActiveRecord用户的一个基本问题,很高兴知道是否有任何标准方法来处理这些异常.我将在下面提出我的建议;请评论或提供替代方案;谢谢!

解决方法

解析错误消息并不是那么糟糕,但感觉很糟糕.我碰到的一个建议(不记得在哪里)似乎很有吸引力,在救援区你可以检查数据库,看看是否确实存在重复记录.如果有,那么StatementInvalid可能是因为重复而你可以相应地处理它.如果没有,那么StatementInvalid必须来自其他东西,并且您必须以不同方式处理它.

所以基本的想法,假设在recipe.name上的唯一索引如上:

begin
  recipe.save!
rescue ActiveRecord::StatementInvalid
  if Recipe.count(:conditions => {:name => recipe.name}) > 0
    # It's a duplicate
  else
    # Not a duplicate; something else went wrong
  end
end

我尝试使用以下内容自动执行此检查:

class ActiveRecord::Base
  def violates_unique_index?(opts={})
    raise unless connection
    unique_indexes = connection.indexes(self.class.table_name).select{|i|i.unique}
    unique_indexes.each do |ui|
      conditions = {}
      ui.columns.each do |col|
        conditions[col] = send(col)
      end
      next if conditions.values.any?{|c|c.nil?} and !opts[:unique_includes_nil]
      return true if self.class.count(:conditions => conditions) > 0
    end
    return false
  end
end

所以现在你应该能够使用generic_record.violates_unique_index?在你的救援块中决定如何处理StatementInvalid.

希望有所帮助!其他方法?

相关文章

validates:conclusion,:presence=>true,:inclusion=>{...
一、redis集群搭建redis3.0以前,提供了Sentinel工具来监控各...
分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣...
上一篇博文 ruby传参之引用类型 里边定义了一个方法名 mo...
一编程与编程语言 什么是编程语言? 能够被计算机所识别的表...
Ruby类和对象Ruby是一种完美的面向对象编程语言。面向对象编...