【问题标题】:What's the purpose of having both db level and code level uniqueness validations in Rails?在 Rails 中同时进行数据库级别和代码级别唯一性验证的目的是什么?
【发布时间】:2017-06-05 20:52:06
【问题描述】:

我看到一个在 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

两者兼得有什么好处?

【问题讨论】:

  • 这在Concurrency and integrity中解释得很清楚
  • 我认为它解释了为什么需要数据库级别的验证,但我的问题也是问为什么我们不能只进行数据库级别的验证。
  • @Jwan622 我更新了我的答案来解决你的问题。

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


【解决方案1】:

引用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

【讨论】:

    【解决方案2】:

    两者兼得有什么好处?

    除了明显的“额外安全”方面,validates_uniqueness_of 在您的应用端添加了一个质量门,以便在尝试提交之前进行检查。

    如果没有validates_uniqueness_of,它会尝试查询数据库来创建,所以从数据库返回错误。

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

    还有一些值得考虑的事情...... 我可能会在以后看到问题,您从数据库中删除了该约束,但是等等..您的模型仍然具有该约束!或相反亦然。

    【讨论】:

      【解决方案3】:

      两者兼得有什么好处?

      db 级防护是终极保护。它使您的数据保持一致。

      应用级别的守卫在进行一些检查的同时,不能保证您的数据保持一致。它的目的是为 ActiveRecord 验证提供动力(填充错误数组,以便您可以显示漂亮的表单错误等)

      所以,如果你只有 db 守卫,你必须做额外的工作来拦截 db 错误并显示用户友好的表单错误。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-20
        • 1970-01-01
        • 1970-01-01
        • 2018-03-23
        • 1970-01-01
        • 2022-10-23
        相关资源
        最近更新 更多