Chi*_*aur 6 database indexing activerecord ruby-on-rails exception
ActiveRecord validates_uniqueness_of很容易受到竞争条件的影响.要真正确保唯一性,需要额外的保护措施.ActiveRecord RDocs的一个建议是在数据库上创建一个唯一索引,例如通过包含在您的迁移中:
add_index :recipes, :name, :unique => true
Run Code Online (Sandbox Code Playgroud)
这将确保在数据库级别名称是唯一的.但这种方法的一个缺点是,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
Run Code Online (Sandbox Code Playgroud)
我尝试使用以下内容自动执行此检查:
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
Run Code Online (Sandbox Code Playgroud)
所以现在你应该可以generic_record.violates_unique_index?在你的救援块中使用来决定如何处理StatementInvalid.
希望有所帮助!其他方法?