真正的唯一性验证

每个使用Ruby On Rails的黑客熟悉ORM ActiveRecord 。 我们将从方框中讨论一种提议的验证,即唯一性验证,以及为什么database_validations gem将保存数据库的一致性。

假设您有一个在电子邮件字段中具有唯一性的用户模型,即

class User < ApplicationRecord validates :email, uniqueness: true end 

您可能已经知道此验证执行以下请求

 SELECT 1 FROM users WHERE email = $1 

每次我们尝试将记录保存到数据库中时。

这种方法有几个缺点:

首先 ,执行一个附加请求,并且如果在模型中初始化了几个唯一性验证,则将针对每个请求执行该请求。 这不是很有效,如果我们希望这些查询快速执行,还需要索引。

其次 ,由于可能争用数据 ,因此该解决方案不能保证唯一性。 几个竞争性运营机构可以同时了解缺少特定记录的信息,从而可以保留相同的数据。

当然,可以通过在数据库级别添加唯一性约束来解决罕见的数据争用情况。 但是在这种情况下,您将不会收到验证错误,对数据库的查询将完全失败,整个事务将被回滚。

在这种情况下,gem database_validations将有所帮助,它在数据库限制和验证之间提供了兼容性。

以下代码显示了gem的主要含义:

 def save(options = {}) ActiveRecord::Base.connection.transaction(requires_new: true) { super } rescue ActiveRecord::RecordNotUnique => e Helpers.handle_unique_error!(self, e) false end 


因此,我们尝试保存数据,如果通过所有其他验证,如果事务失败并回滚,我们将分析错误并将正确的值分配给对象的errors

在查看了文档基准之后 ,我们可以得出结论,这个gem将加速将记录保存到数据库的过程至少两次。

得益于PostgreSQLSQLiteMySQL等数据库的支持以及与validates_uniqueness_of向后兼容性,用validates_uniqueness_of进行替换的过程只需几分钟。

开箱即用的还有RSpec的便捷匹配器:

 specify do expect(described_class) .to validate_db_uniqueness_of(:field) .with_message('duplicate') .with_where('(some_field IS NULL)') .scoped_to(:another_field) .with_index(:unique_index) end 

当切换到新的验证时,您需要对数据库中的唯一性进行限制,但是如果尚未在数据库中进行限制,gem将在应用程序启动时指出这一点。

该宝石在一个应用程序中进行了测试,该应用程序经过100多个验证,在50多个模型中具有唯一性。

使用gem并分享您的意见。 欢迎为进一步发展做出任何贡献!

Source: https://habr.com/ru/post/zh-CN431298/


All Articles