Reading / Coding / Hacking
在 Rails 中如果要验证数据字段值的唯一性通常都会用到 validates_uniqueness_of
方法,比如我要确保用户注册时填写的 Email 地址是唯一的,就会这样写:
class User < ActiveRecord::Base
validates_uniqueness_of :email
end
如果了解 validates_uniqueness_of
的实现细节的话就会知道,单纯这样写无法完全保证 Email 值的唯一性,因为 ActiveRecord::Validations::UniquenessValidator
的执行与随后写入数据到数据库这两个操作之间存在时间差,在多用户并发访问的情况下就很容易写入重复的值。解决方法就是给 email 字段建一个 unique 的索引,把唯一性检查交给数据库来做,这样如果插入重复值而 validates_uniqueness_of
又没有检查到的话,就会抛 ActiveRecord::RecordNotUnique
异常。
其实这一点在 Rails 官方文档 的 Concurrency and integrity 一节中有详细的讲解,我要说的是另一个问题,那就是既然 validates_uniqueness_of
不可靠,为什么不省掉这一步,直接捕捉(可能会抛出的)ActiveRecord::RecordNotUnique
异常?
前几天在 Ruby China 问了这个问题(链接),根据大家的回复总结出的答案就是“方便 form validation error messages 的显示”,因为 ActiveRecord::RecordNotUnique
这个异常没有包含太多信息(比如到底是哪个字段重复了),无法给予用户必要的提示。
所以结论是 validates_uniqueness_of
要写,而 ActiveRecord::RecordNotUnique
这个异常也要在 controller 里面捕捉并适当处理,比如这样(没有实际试验过):
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordNotUnique, with: :recheck_uniqueness
private
def recheck_uniqueness
# trigger validations again so that duplication can be detected
@user.valid?
end
end