为什么你应该停止在 Ruby on Rails 中使用 ActiveModel 验证

为什么你应该停止在 Ruby on Rails 中使用 ActiveModel 验证

关于切换好处的深入指南

drake meme, saying no — caption : activemodel validations. saying yes — postgresql constraints

image from imgflip

Ruby on Rails 在很多事情上都很棒,而且正如你们中的许多人所知,模型中的验证很酷,因为它们简单、实现快速且易于测试。

但他们也很烂。

注意:本文仅适用于您使用 PostgreSQL 的情况。如果您使用的是 SQLite 或 MySQL,请停止。

这些是什么?

验证是类似的方法[ 验证](https://guides.rubyonrails.org/active_record_validations.html#validations-overview) ,[ validates_related](https://guides.rubyonrails.org/active_record_validations.html#validates-associated) 等等,它将根据某些逻辑(可以是唯一性、字符串的格式或您自己的自定义逻辑)检查实例,并允许或禁止将其插入数据库。

使用模型验证有很多缺点。让我们一起来看看吧!

它只在 Rails 层可行

假设您的数据库在多个团队之间共享。您的数据团队直接插入您的主数据库,或者其他系统可以与数据库交互而无需通过您的 API。因此,您的验证和回调将被绕过,您可以在数据库中拥有对您的 RDBMS(例如 Postgresql)有效但对您的 rails 应用程序无效的数据。它可以“默默地”瘫痪你的系统。

实施后更改验证规则是不安全的

如果开发人员添加或更改任何验证规则,则可能会在新旧数据库规则之间产生差异。以前认为有效的记录现在将被视为无效而没有任何警告!这可能会使整个数

“好吧,我知道这可能很麻烦。你建议我们做什么来解决这个问题?”

PostgreSQL 的限制,当然!

一个基本的例子——让我们把手弄脏!

这些是什么?

您应用于数据库模式以插入值(如验证)的“规则”。

让我们看一个快速示例,说明如何将简单的验证规则转换为约束。

  • 让我们建立一个快速项目来使用这段代码:

    rails new constraint_example --database=postgresql

  • cd进去:

    cd 约束示例

  • 对于这个例子,我们需要一个模型。让我们称之为 用户 (我知道,多么原始)。

    bin/rails 生成模型用户电子邮件:字符串年龄:整数

上面的命令应该创建一个如下所示的迁移文件:

现在启动 bin/rails 数据库:创建数据库:迁移 .这将从迁移中创建 db 和 user 表。

让我们来看看 User 模型吧!

默认情况下,它应该几乎是空的:

 类用户 < 申请记录  
 结尾

我们需要改变它,所以我们所有的用户都有独特的电子邮件。使用 rails 验证,它看起来像这样:

 类用户 < 申请记录  
 验证:电子邮件,唯一性:真  
 结尾

这将告诉 Rails 检查所有电子邮件 用户 表并在创建或更新之前查看它是否已经存在 用户 记录。

让我们创建我们的第一个 用户 记录!

1.启动一个rails控制台: 垃圾箱/导轨 c

2.创建一个简单的用户: User.create(电子邮件:'[[email protected]](/cdn-cgi/l/email-protection)',年龄:42)

3. 看看 rails 控制台日志告诉你什么。我们应该有如下所示的日志:

看到第二行了吗?由于我们的验证规则,Rails 将执行查询以检查数据库中是否存在记录 应用程序/模型/user.rb .

Rails 看到没有返回记录,所以它创建一个并返回新创建的实例!太好了,我们可以说。

当然,这么少的代码就能得到这样的结果真是太棒了!那么有什么问题呢?

我们每次在没有索引的列上创建或更新新用户时都会启动一个查询。如果您的数据库可扩展并容纳数百万 用户 记录,这可能导致 插入 比他们需要的要慢得多,因为他们总是会在前面加上一个 选择 在没有索引的列上。这是第一个问题。

第二个问题是,只有当您尝试通过您的 rails 应用程序创建/更新记录时,才会执行此查询。如果您有其他应用程序会在同一数据库上创建数据,它们将忽略 Rails 验证,因为它们不知道这一点。假设我们直接从数据库中使用同一电子邮件创建第二条记录:

  1. 访问您的数据库: psql -d 约束示例开发

  2. 创建另一个记录:

    插入用户(id、电子邮件、年龄、created_at、updated_at)值(2,' 简单@example.com ', 25, '2022-09-10 11:19:20.755912', '2022-09-10 11:19:20.755912');

由于它在数据库级别,Postgres 绕过 Rails 层并跳过 Rails 验证。第二条记录创建成功。

3. 现在,回到 Rails 控制台: 垃圾箱/导轨 c

4. 更新第一个用户的年龄! User.first.update!(年龄:53)

看最后一行:

 /Users/yorick/.rvm/gems/ruby-2.7.4/gems/activerecord-7.0.3.1/lib/active_record/validations.rb:80:in `raise_validation_error':验证失败:电子邮件已被使用(ActiveRecord: :记录无效)

我们有一个错误告诉我们电子邮件无效,但我们没有更改它的电子邮件!

Rails 不在乎。

如果只有 rails 拥有数据库的密钥,并且您从不打算通过 rails 以外的其他方式操作数据,那么 rails 验证是一种“可接受的”方式。

让我们尝试不同的方法——使用 PostgreSQL 的约束和索引!

  1. 让我们创建一个迁移: bin/rails g 迁移 AddEmailConstraintToUsers
  2. 对于这样一个简单的情况,我们可以使用 rails 方式:

我知道,我一直在谈论约束,但我们为什么要使用 添加索引 这里?创建一个 unique_constraint 与创建一个相同 唯一索引 .基于 PostgreSQL 官方文档

当为表定义唯一约束或主键时,PostgreSQL 会自动创建唯一索引。索引涵盖构成主键或唯一约束的列(多列索引,如果适用),并且是强制执行约束的机制。

所以创建唯一约束与创建唯一索引是一样的。

还记得 Rails 过去是如何在我们每次想要创建或更新记录时进行查询的吗? Postgres 现在会自己做同样的事情,但速度更快,这要归功于索引。

好的,现在让我们运行迁移!

bin/rails 数据库:迁移

看起来我们有一个错误,如下所示:

 造成的:  
 PG::UniqueViolation:错误:无法创建唯一索引“index_users_on_email”  
 详细信息:密钥(电子邮件)=([ 简单@example.com](/cdn-cgi/l/email-protection#bac9d3d7cad6dffadfc2dbd7cad6df94d9d5d7) ) 重复。  
 /Users/yorick/Work/constraint_ecample/db/migrate/20220910115344_add_email_constraint_to_users.rb:3:in `change'  
 任务:TOP => db:migrate  
 (通过使用 --trace 运行任务查看完整跟踪)

作为 文档 说:

当索引被声明为唯一时,不允许具有相同索引值的多个表行

这意味着我们必须在添加该约束之前进行清理。这是您将来希望迁移到 PG 约束时必须考虑的事情

让我们使用以下代码进行清理:

现在,启动迁移 bin/rails 数据库:迁移 .

让我们回到我们的 应用程序/模型/user.rb 并删除我们添加的验证:

 类用户 < ApplicationRecord 结尾

现在,让我们回到 Rails 控制台并添加以下代码:

The SELECT before Create has disappeared.

Rails 不再独立进行查询,因为它不再意识到这个约束。该工作留给 Postgres 来验证记录的创建。

只是为了它,让我们尝试验证我们不能使用相同的电子邮件创建第二个用户。这是代码:

It still raises ActiveRecord::RecordNotUnique because ActiveRecord will catch PG’s PG::UniqueViolation

现在,让我们尝试在 PostgreSQL 级别创建一个副本。

  1. 启动 psql 控制台:

    psql -d test_app_development

2.尝试创建重复记录:

 插入用户(id、电子邮件、年龄、created_at、updated_at)值(1,'[ 简单@example.com](/cdn-cgi/l/email-protection#f98a909489959cb99c81989489959cd79a9694) ', 42, '2022-09-10 11:19:20.755912', '2022-09-10 11:19:20.755912');  
 错误:重复键值违反唯一约束“index_users_on_email”  
 详细信息:密钥(电子邮件)=([ 简单@example.com](/cdn-cgi/l/email-protection#4a3923273a262f0a2f322b273a262f64292527) ) 已经存在。

有用!您已经创建了一个比 Rails 验证更快、更安全的简单约束!

使用 PG 约束的缺点——以及如何应对它们

我在本文开头提到了 Rails 验证的缺点。现在让我们尝试对 PostgreSQL 约束做同样的事情。

它混淆了数据模型的一些关键逻辑概念

要了解模型的约束,您必须深入研究 架构.rb 看看它是否有任何验证。对于大型应用程序,有一个正常的 架构.rb 文件是一千行长并且不是很容易理解的文件。

为了解决这个问题,我建议添加 awesome[ 注释](https://github.com/ctran/annotate_models) gem 到您的 Gemfile 中 发展 团体:

 组:开发做 ...  
 宝石'注释'  
 ...  
 结尾

然后,运行 捆绑安装 , 其次是

 bin/rails g 注释:安装

最后,运行 捆绑执行注释 , 如下所示:

annotate has notes on models, which is very useful!

注释 gem 将跟踪对表的更改,并在表模型的顶部打印其列、索引和约束的列表。

更改约束需要迁移

由于它在您的数据库模式中,因此您不能再通过简单的提交来更改规则,仅此而已。您必须创建一个迁移;如果某些数据不符合约束,迁移将失败。有人会认为这是使用 PG 约束的一个陷阱,但我会说它更好。

本文以唯一约束为例,但可以添加其他约束,如检查约束、NOT NULL 约束等。可以在官方了解更多 关于约束的 PostgreSQL 文档 .基本上,使用 ActiveRecord Validation 可以完成的所有事情都可以通过 PostgreSQL 约束来完成!

接下来 : 为什么以及如何摆脱 活动记录 Ruby on Rails 中的回调!敬请期待。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/32220/46441300

posted @ 2022-09-13 00:47  哈哈哈来了啊啊啊  阅读(20)  评论(0编辑  收藏  举报