引用excellent article by thoughtbot:
当您持久化用户实例时,Rails 将通过运行 SELECT 查询来验证您的模型,以查看提供的电子邮件中是否已经存在任何用户记录。假设记录被证明是有效的,Rails 将运行 INSERT 语句来持久化用户。这在开发中非常有用,如果您正在运行单进程 Web 服务器的单个实例,甚至可以在生产中使用。
但你不是在运行一个单独的 WEBrick 实例,是吗?不,为了最大化每分钟的请求数,你在多个 Heroku dyno 上运行 Unicorn,每个都有多个 Web 进程。让我们看看如果其中两个进程几乎同时尝试创建具有相同电子邮件地址的用户会发生什么:
...您可以在生成迁移或模型时创建唯一索引:
rails generate model user email:string:uniq
有了索引,上面的场景现在会如何发展?
现在,我们将数据库作为我们对抗不一致数据的最后一道防线。第二次保存操作会产生一个 ActiveRecord::RecordNotUnique 异常。
结论
Rails 做了很多事情,但数据完整性验证不是其中之一。您的关系数据库旨在强制执行数据完整性;放手吧。
编辑
除了 db 约束(根据 OP 的评论),添加了为什么要使用 validates_uniqueness_of 的示例:
我认为它解释了为什么需要数据库级验证,但我的问题也是问为什么我们不能只进行数据库级验证。
回答
进一步引用thoughtbot's article:
第二次保存操作会产生一个 ActiveRecord::RecordNotUnique 异常。在大多数情况下,这将导致应用程序错误。如果您需要提供更好的体验,您可以在控制器操作中救援和处理该异常,或在类级别使用rescue_from。
让我们在课堂上看到这一点。
仅使用数据库级约束:
class Foo < ApplicationRecord
belongs_to :bar
validates_presence_of :name
end
class Bar < ApplicationRecord
has_many :foos
end
> Foo.create!(bar_id: 1, name: 'Poop')
> Foo.create!(bar_id: 1, name: 'Poop')
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_foos_on..."
使用数据库约束和应用程序级验证(validates_uniqueness_of):
class Foo < ApplicationRecord
belongs_to :bar
validates_presence_of :name
validates_uniqueness_of :name, scope: :bar_id
end
> Foo.create!(bar_id: 1, name: 'Poop')
> Foo.create!(bar_id: 1, name: 'Poop')
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken