发布于 2026-01-06 6 阅读
0

Active Record 中的 10 个新特性

Active Record 中的 10 个新特性

最初发布于Hint 的博客

在这篇文章中,我们将介绍 Rails 6 中 Active Record 的 10 个新增功能。对于每个新增功能,我都会提供该功能所在的 PR 链接、作者的 GitHub 个人资料链接以及该功能提供的简要说明。

我们有很多内容要讲,那就开始吧!

1.rails db:prepare

PR 35678,作者:@robertomiranda

运行此 rake 任务时,如果数据库存在,则运行所有待处理的迁移。如果数据库不存在,则运行db:setuprake 任务本身。

此功能设计为幂等的,允许反复运行直到成功完成。

rake db:prepare任务的功能如下:

rake db:prepare 流程图

如果你还不熟悉db:setuprake 任务:

  • 创建数据库
  • 加载文件schema.rbstructure.sql取决于您的应用程序配置使用的文件)。
  • 运行该任务,该任务会执行文件db:seed中的代码。db/seeds.rb

2.rails db:seed:replant

PR 34779,作者:@bogdanvlviv

这个新的db:seed:replantrake 任务做了两件事:

  • truncate会对运行 rake 任务的 Rails 环境中所有由 ActiveRecord 管理的表进行操作。(请注意,此truncate操作会删除表中的所有数据,但不会重置表的自增(ID)计数器。)
  • 运行db:seedRails rake 任务以填充种子数据

3. 自动数据库切换

PR 35073,作者:@eileencodes

Rails 6 提供了一个框架,可以将传入的请求自动路由到主数据库连接或只读副本。

默认情况下,如果距离上次写入请求(任何非写入写入请求)至少 2 秒,则此新功能允许您的应用程序自动将读取请求(GET, )路由到读取 Relica 数据库。HEADGETHEAD

默认情况下,用于指定何时将读取请求路由到副本的逻辑是在解析器类中指定的,ActiveRecord::Middleware::DatabaseSelector::Resolver如果您想要自定义行为,则可以覆盖该解析器类。

中间件还提供了一个会话类,ActiveRecord::Middleware::DatabaseSelector::Resolver::Session用于跟踪上次写入请求的时间。与解析器一样,该类也可以被重写。

要启用默认行为,您需要将以下配置选项添加到应用程序的某个环境文件中,config/environments/production.rb例如:

config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = 
  ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_operations = 
  ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
Enter fullscreen mode Exit fullscreen mode

如果您决定覆盖默认功能,可以使用这些配置选项来指定您想要使用的延迟、自定义解析器类的名称和自定义会话类的名称,这两个类都应该是默认类的子类。

4. 枚举的否定作用域

PR 35381@dhh提供

虽然enum传统上提供了按枚举值查找项的作用域,但它没有提供查找与特定枚举值不匹配的项的作用域。

例如,给定Post一个博客模型,其字段具有枚举类型status

enum status %i(draft published archived)
Enter fullscreen mode Exit fullscreen mode

以下范围已自动提供一段时间:

scope :draft, -> { where(status: 0) }
scope :published, -> { where(status: 1) }
scope :archived, -> { where(status: 2) }
Enter fullscreen mode Exit fullscreen mode

现在,还可以使用以下负片示波器:

scope :not_draft, -> { where.not(status: 0) }
scope :not_published, -> { where.not(status: 1) }
scope :not_archived, -> { where.not(status: 2) }
Enter fullscreen mode Exit fullscreen mode

例如,这使得查找未发布的文章变得很容易:

Post.not_published
Enter fullscreen mode Exit fullscreen mode

5.#extract_associated

PR 35784,作者:@dhh

新方法实际上只是加号#extract_associated的简写preloadmap/collect

以下是该方法的源代码:

def extract_associated(association)
  preload(association).collect(&association)
end
Enter fullscreen mode Exit fullscreen mode

请注意,它preload不允许您指定关联“预加载”的条件。您需要使用不同的预加载机制,例如 `get_extainer_reloaded`或 ` includesget_extainer_reloaded` eager_loadjoins

用法#extract_associated可能如下所示:

commented_posts = user.comments.extract_associated(:post)
Enter fullscreen mode Exit fullscreen mode

6.#annotate

PR 35617,作者:@mattyoho

这是一个非常实用的新功能,可用于向应用程序的日志文件中添加有用的信息。该#annotate方法提供了一种机制,可以将注释嵌入到 ActiveRecord 查询生成的 SQL 语句中。此外,它生成的注释还可以完全动态化。

像下面这样插入annotate到查询链中:

User
  .annotate('there can be only one!')
  .find_by(highlander: true)
Enter fullscreen mode Exit fullscreen mode

将生成以下 SQL 语句:

SELECT "users".*
FROM "users"
WHERE "users"."highlander" = ? /* there can be only one! */
LIMIT ? [["highlander", 1], ["LIMIT", 1]]
Enter fullscreen mode Exit fullscreen mode

7.#touch_all

PR 31513,作者:@fatkodima

另一种ActiveRecord::Relation方法是#touch_all修改当前范围内的所有记录,更新它们的时间戳。

你可以向 touch 传递一个列数组,并可选择提供要使用的时间值。touch_all默认值为应用程序配置中设置的时区的当前时间config.active_record.default_timezone(该设置默认为 UTC)。

例如,要更新updated_at与特定博客文章关联的所有评论字段@post,您可以:

@post.comments.touch_all
Enter fullscreen mode Exit fullscreen mode

要更新评论中的某个字段,例如:reviewed_at,您需要提供列名:

@post.comments.touch_all(:reviewed_at)
Enter fullscreen mode Exit fullscreen mode

要指定时间值,您需要:

@post.comments.touch_all(:reviewed_at, time: the_time)
Enter fullscreen mode Exit fullscreen mode

8.#destroy_by#delete_by

PR 35316,作者:@abhaynikam

这些destroy_by方法delete_by旨在's方法提供对称性( “精神上” )。ActiveRecordfind_byfind_or_create_by

我认为你应该注意一个重要的区别。`returns find_byone record` 或 `returns` nil,而destroy_by`and`delete_by将匹配整个记录集合。

使用方法find_by如下:

User.find_by(admin: true)
Enter fullscreen mode Exit fullscreen mode

生成以下 SQL 语句:

SELECT  "users".*
FROM "users"
WHERE "users"."admin" = $1
LIMIT 1  [["admin", 1]]
Enter fullscreen mode Exit fullscreen mode

然而,使用delete_by相同的参数:

User.delete_by(admin: true)
Enter fullscreen mode Exit fullscreen mode

结果生成以下 SQL 语句:

DELETE FROM "users"
WHERE "users"."admin" = ?  [["admin", 1]]
Enter fullscreen mode Exit fullscreen mode

使用这些方法时,一定要牢记这一点!

另外,值得注意的是,这些方法没有带感叹号(!)的版本delete_by/destroy_by

9. 无尽的范围#where

PR 34906,作者:@gregnavis

Ruby 2.6 引入了无限范围。这项新特性允许你在 Rails 的#where条件语句中使用它们。

例如,当尝试查找评论数超过 10 条的帖子时(我知道这是一个牵强的例子)。

以前你需要使用类似这样的 SQL:

Post.where('num_comments > ?', 10)
Enter fullscreen mode Exit fullscreen mode

现在你可以使用更符合 Ruby 惯用的语法了:

User.where(num_comments: (10..))
Enter fullscreen mode Exit fullscreen mode

10. 隐式排序

PR 34480,作者:@tekin

此功能允许为数据库表配置隐式排序。Rails 默认按表的主键对结果进行隐式排序,但当主键不是自增整数(例如 UUID)时,可能会产生意想不到的结果。

设置表的隐式排序列允许您指定默认顺序,而无需使用默认范围,这意味着您无需reorder在代码中使用来更改下游的顺序,您可以使用order(假设您尚未在查询链的前面指定显式排序)。

例如,如果您在Post表中声明了隐式排序:

class Post < ActiveRecord::Base
  self.implicit_order_column = 'title'
end
Enter fullscreen mode Exit fullscreen mode

请注意,如果您对不能保证唯一值的列声明隐式排序,则结果可能与您预期的不同。

文章来源:https://dev.to/hint/10-new-things-in-active-record-5112