在Rails中同时具有数据库级别和代码级别唯一性验证的目的是什么?

时间:2017-06-05 20:52:06

标签: ruby-on-rails validation unique-index

我看到一个在Rails中同时具有数据库级和代码级验证的表。

CREATE UNIQUE INDEX some_index_name ON joined_table_name USING btree (user_id, restriction_id);

和代码级别:

class SomeJoinTable
validates_uniqueness_of :restriction_id, :scope => :user_id
end

同时拥有两者的好处是什么?

3 个答案:

答案 0 :(得分:6)

引用excellent article by thoughtbot

  

当您持久保存用户实例时,Rails将通过运行SELECT查询来验证您的模型,以查看所提供的电子邮件是否已存在任何用户记录。假设记录证明有效,Rails将运行INSERT语句以持久保存用户。这在开发中非常有用,如果您运行单个进程Web服务器的单个实例,甚至可以在生产中工作。

     

但是你没有运行WEBrick的单独实例,是吗?不,为了最大化每分钟的请求,您在多个Heroku dynos上运行Unicorn,每个都有多个Web进程。让我们来看看如果只有两个进程试图在同一时间创建具有相同电子邮件地址的用户会发生什么:

thoughtbot example image

  

...您可以在使用以下内容生成迁移或模型时创建唯一索引:

rails generate model user email:string:uniq

  

有了索引,上面的场景现在如何发挥作用?

thoughtbot example image 2

  

现在我们有数据库作为我们对不一致数据的战争的最后一道防线。第二个保存操作将生成ActiveRecord :: RecordNotUnique异常。

     

结论

     

Rails做了很多事情,但数据完整性验证不是其中之一。您的关系数据库旨在强制数据完整性;让它。

修改

添加了除了db约束之外你还要使用validates_uniqueness_of的原因示例(根据OP的评论):

  

我认为它解释了为什么需要数据库级验证,但我的问题也是问为什么我们不能进行数据库级验证。

<强> ANSWER

进一步引用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..."

使用数据库约束和应用程序级验证(la 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

答案 1 :(得分:0)

  

同时拥有两者有什么好处?

除了显而易见的“额外安全性”方面,让validates_uniqueness_of在您的应用程序端添加质量门,以便在尝试提交之前进行检查。

如果没有validates_uniqueness_of ,它将尝试查询要创建的数据库,因此错误将从数据库返回。

使用validates_uniqueness_of ,它将在尝试创建之前查询数据库以查看它是否存在,因此错误将从应用返回。由于您可以完全控制,因此最好从应用程序返回错误。

还要考虑一下...... 我可能会在以后看到问题,你从数据库中删除了这个约束,但是等等..你的模型仍然有这个约束!或相反亦然。

答案 2 :(得分:0)

  

同时拥有两者的好处是什么?

db级别的防护是最终的保护。这是保持数据一致的原因。

应用程序级别的警卫虽然也做了一些检查,但不能依赖它来保持数据的一致性。其目的是支持ActiveRecord验证(填充错误数组,以便显示漂亮的表单错误等)。

因此,如果您只有数据库防护,则必须做额外的工作来拦截数据库错误并显示用户友好的表单错误。

如果你只有应用程序级别的检查,你也可以删除它。它很容易受到竞争条件的影响,迟早会发生 违规行为。