1. 升级到 Rails 7.1
如果您正在升级现有应用程序,最好在升级之前有良好的测试覆盖率。您还应该首先升级到 Rails 7.0(如果您还没有),并确保您的应用程序在尝试升级到 Rails 7.1 之前仍然按预期运行。在升级 Ruby on Rails 指南中提供了升级时需要注意的事项列表。
2. 主要特性
2.1. 为新的 Rails 应用程序生成 Dockerfile
新的 Rails 应用程序默认支持 Docker。在生成新应用程序时,Rails 现在将在应用程序中包含与 Docker 相关的文件。
这些文件作为使用 Docker 在生产环境中部署 Rails 应用程序的基础设置。需要注意的是,这些文件不用于开发目的。
以下是使用这些 Docker 文件构建和运行 Rails 应用程序的快速示例
$ docker build -t app .
$ docker volume create app-storage
$ docker run --rm -it -v app-storage:/rails/storage -p 3000:3000 --env RAILS_MASTER_KEY=<your-config-master-key> app
您还可以从这个 Docker 镜像启动控制台或运行器
$ docker run --rm -it -v app-storage:/rails/storage --env RAILS_MASTER_KEY=<your-config-master-key> app console
对于那些希望创建多平台镜像(例如,用于 AMD 或 Intel 部署的 Apple Silicon)并将其推送到 Docker Hub 的用户,请按照以下步骤操作
$ docker login -u <your-user>
$ docker buildx create --use
$ docker buildx build --push --platform=linux/amd64,linux/arm64 -t <your-user/image-name> .
此增强功能简化了部署过程,为在生产环境中启动和运行 Rails 应用程序提供了一个便捷的起点。
2.2. 添加 ActiveRecord::Base.normalizes
ActiveRecord::Base.normalizes 声明了一个属性规范化。当属性被赋值或更新时,将应用规范化,并且规范化的值将持久化到数据库中。规范化也应用于查询方法对应的关键字参数,允许使用未规范化的值查询记录。
例如
class User < ActiveRecord::Base
normalizes :email, with: -> email { email.strip.downcase }
normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
end
user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
user.email # => "cruise-control@example.com"
user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
user.email # => "cruise-control@example.com"
user.email_before_type_cast # => "cruise-control@example.com"
User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
2.3. 添加 ActiveRecord::Base.generates_token_for
ActiveRecord::Base.generates_token_for 定义了特定目的的令牌生成。生成的令牌可以过期,也可以嵌入记录数据。当使用令牌获取记录时,将比较令牌中的数据和记录中的当前数据。如果两者不匹配,令牌将被视为无效,就像它已过期一样。
这是一个实现一次性密码重置令牌的示例
class User < ActiveRecord::Base
has_secure_password
generates_token_for :password_reset, expires_in: 15.minutes do
# `password_salt` (defined by `has_secure_password`) returns the salt for
# the password. The salt changes when the password is changed, so the token
# will expire when the password is changed.
password_salt&.last(10)
end
end
user = User.first
token = user.generate_token_for(:password_reset)
User.find_by_token_for(:password_reset, token) # => user
user.update!(password: "new password")
User.find_by_token_for(:password_reset, token) # => nil
2.4. 添加 perform_all_later 以一次性将多个作业入队
Active Job 中的 perform_all_later 方法,旨在简化同时将多个作业入队的过程。这个强大的新增功能允许您有效地将作业入队而不会触发回调。当您需要一次性将一批作业入队时,这尤其有用,减少了多次往返于队列数据存储的开销。
以下是您如何利用 perform_all_later
# Enqueueing individual jobs
ActiveJob.perform_all_later(MyJob.new("hello", 42), MyJob.new("world", 0))
# Enqueueing an array of jobs
user_jobs = User.pluck(:id).map { |id| UserJob.new(user_id: id) }
ActiveJob.perform_all_later(user_jobs)
通过利用 perform_all_later,您可以优化作业入队过程,并提高效率,尤其是在处理大量作业时。值得注意的是,对于支持新 enqueue_all 方法的队列适配器(例如 Sidekiq 适配器),入队过程使用 push_bulk 进一步优化。
请注意,此新方法引入了一个单独的事件 enqueue_all.active_job,并且不使用现有的 enqueue.active_job 事件。这确保了批量入队过程的准确跟踪和报告。
2.5. 复合主键
现在数据库和应用程序级别都支持复合主键。Rails 能够直接从 Schema 中派生这些键。此功能对于多对多关系和其他复杂的数据模型特别有益,在这些模型中,单个列不足以唯一标识记录。
Active Record 中查询方法(例如 #reload、#update、#delete)生成的 SQL 将包含复合主键的所有部分。#first 和 #last 等方法将在 ORDER BY 语句中使用完整的复合主键。
query_constraints 宏可以用作“虚拟主键”,以实现相同的行为而无需修改数据库 Schema。例如
class TravelRoute < ActiveRecord::Base
query_constraints :origin, :destination
end
同样,关联接受一个 query_constraints: 选项。此选项用作复合外键,配置用于访问关联记录的列列表。
示例
class TravelRouteReview < ActiveRecord::Base
belongs_to :travel_route, query_constraints: [:travel_route_origin, :travel_route_destination]
end
2.6. 引入 Trilogy 适配器
引入了一个新适配器,以促进 Trilogy(一个 MySQL 兼容的数据库客户端)与 Rails 应用程序的无缝集成。现在,Rails 应用程序可以通过配置其 config/database.yml 文件来选择集成 Trilogy 功能。例如
development:
adapter: trilogy
database: blog_development
pool: 5
或者,可以使用 DATABASE_URL 环境变量实现集成
ENV["DATABASE_URL"] # => "trilogy:///blog_development?pool=5"
2.7. 添加 ActiveSupport::MessagePack
ActiveSupport::MessagePack 是一个与 msgpack gem 集成的序列化器。ActiveSupport::MessagePack 可以序列化 msgpack 支持的基本 Ruby 类型,以及一些附加类型,例如 Time、ActiveSupport::TimeWithZone 和 ActiveSupport::HashWithIndifferentAccess。与 JSON 和 Marshal 相比,ActiveSupport::MessagePack 可以减小有效负载大小并提高性能。
ActiveSupport::MessagePack 可以用作消息序列化器
config.active_support.message_serializer = :message_pack
# Or individually:
ActiveSupport::MessageEncryptor.new(secret, serializer: :message_pack)
ActiveSupport::MessageVerifier.new(secret, serializer: :message_pack)
config.action_dispatch.cookies_serializer = :message_pack
并作为缓存序列化器
config.cache_store = :file_store, "tmp/cache", { serializer: :message_pack }
# Or individually:
ActiveSupport::Cache.lookup_store(:file_store, "tmp/cache", serializer: :message_pack)
2.8. 引入 config.autoload_lib 和 config.autoload_lib_once 以增强自动加载
引入了一个新的配置方法 config.autoload_lib(ignore:)。此方法用于通过包含默认不包含的 lib 目录来增强应用程序的自动加载路径。此外,为新应用程序生成了 config.autoload_lib(ignore: %w(assets tasks))。
从 config/application.rb 或 config/environments/*.rb 调用时,此方法会将 lib 目录添加到 config.autoload_paths 和 config.eager_load_paths。需要注意的是,此功能不适用于引擎。
为了确保灵活性,可以使用 ignore 关键字参数来指定 lib 目录中不应由自动加载器管理的子目录。例如,您可以通过将 assets、tasks 和 generators 等目录传递给 ignore 参数来排除它们
config.autoload_lib(ignore: %w(assets tasks generators))
config.autoload_lib_once 方法与 config.autoload_lib 类似,不同之处在于它将 lib 添加到 config.autoload_once_paths。
请参阅自动加载指南了解更多详细信息
2.9. 用于通用异步查询的 Active Record API
Active Record API 引入了一项重大增强,扩展了其对异步查询的支持。此增强功能解决了对非快速查询更高效处理的需求,尤其关注聚合(例如 count、sum 等)以及所有返回单个记录或除 Relation 以外的任何内容的方法。
新 API 包含以下异步方法
async_countasync_sumasync_minimumasync_maximumasync_averageasync_pluckasync_pickasync_idsasync_find_by_sqlasync_count_by_sql
以下是一个简短的示例,演示如何使用其中一个方法 async_count 以异步方式计算已发布帖子的数量
# Synchronous count
published_count = Post.where(published: true).count # => 10
# Asynchronous count
promise = Post.where(published: true).async_count # => #<ActiveRecord::Promise status=pending>
promise.value # => 10
这些方法允许以异步方式执行这些操作,这可以显著提高某些类型数据库查询的性能。
2.10. 允许模板设置严格的 locals
引入一项新功能,允许模板设置显式的 locals。此增强功能在向模板传递变量时提供了更大的控制和清晰度。
默认情况下,模板将接受任何 locals 作为关键字参数。但是,现在您可以通过在模板文件开头添加 locals 魔术注释来定义模板应接受哪些 locals。
工作原理如下
<%# locals: (message:) -%>
<%= message %>
您还可以为这些 locals 设置默认值
<%# locals: (message: "Hello, world!") -%>
<%= message %>
可选关键字参数可以展开
<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>
如果您想完全禁用 locals 的使用,可以这样做
<%# locals: () %>
Action View 将在任何支持 # 前缀注释的模板引擎中处理 locals: 魔术注释,并将从 partial 中的任何行读取魔术注释。
只支持关键字参数。定义位置或块参数将在渲染时引发 Action View Error。
2.11. 添加 Rails.application.deprecators
新的Rails.application.deprecators 方法返回应用程序中托管的弃用器集合,并允许您轻松添加和检索单个弃用器
Rails.application.deprecators[:my_gem] = ActiveSupport::Deprecation.new("2.0", "MyGem")
Rails.application.deprecators[:other_gem] = ActiveSupport::Deprecation.new("3.0", "OtherGem")
集合的配置设置会影响集合中的所有弃用器。
Rails.application.deprecators.debug = true
Rails.application.deprecators[:my_gem].debug
# => true
Rails.application.deprecators[:other_gem].debug
# => true
在某些情况下,您可能希望在特定代码块中禁用所有弃用器警告。使用弃用器集合,您可以轻松地在块内禁用所有弃用器警告
Rails.application.deprecators.silence do
Rails.application.deprecators[:my_gem].warn # No warning (silenced)
Rails.application.deprecators[:other_gem].warn # No warning (silenced)
end
2.12. 支持 JSON response.parsed_body 的模式匹配
当 ActionDispatch::IntegrationTest 测试块为 JSON 响应调用 response.parsed_body 时,其有效负载将具有不区分大小写的访问权限。这使得与 Ruby 的模式匹配 以及 Minitest 内置的模式匹配支持 集成成为可能
get "/posts.json"
response.content_type # => "application/json; charset=utf-8"
response.parsed_body.class # => Array
response.parsed_body # => [{"id"=>42, "title"=>"Title"},...
assert_pattern { response.parsed_body => [{ id: 42 }] }
get "/posts/42.json"
response.content_type # => "application/json; charset=utf-8"
response.parsed_body.class # => ActiveSupport::HashWithIndifferentAccess
response.parsed_body # => {"id"=>42, "title"=>"Title"}
assert_pattern { response.parsed_body => [{ title: /title/i }] }
2.13. 扩展 response.parsed_body 以使用 Nokogiri 解析 HTML
扩展 ActionDispatch::Testing 模块 以支持将 HTML response.body 的值解析为 Nokogiri::HTML5::Document 实例
get "/posts"
response.content_type # => "text/html; charset=utf-8"
response.parsed_body.class # => Nokogiri::HTML5::Document
response.parsed_body.to_html # => "<!DOCTYPE html>\n<html>\n..."
新添加的 Nokogiri 模式匹配支持,以及 Minitest 内置的模式匹配支持 为 HTML 响应的结构和内容进行测试断言提供了机会
get "/posts"
html = response.parsed_body # => <html>
# <head></head>
# <body>
# <main><h1>Some main content</h1></main>
# </body>
# </html>
assert_pattern { html.at("main") => { content: "Some main content" } }
assert_pattern { html.at("main") => { content: /content/ } }
assert_pattern { html.at("main") => { children: [{ name: "h1", content: /content/ }] } }
2.14. 引入 ActionView::TestCase.register_parser
扩展 ActionView::TestCase 以支持将视图局部变量渲染的内容解析为已知结构。默认情况下,定义 rendered_html 以将 HTML 解析为 Nokogiri::XML::Node,定义 rendered_json 以将 JSON 解析为 ActiveSupport::HashWithIndifferentAccess
test "renders HTML" do
article = Article.create!(title: "Hello, world")
render partial: "articles/article", locals: { article: article }
assert_pattern { rendered_html.at("main h1") => { content: "Hello, world" } }
end
test "renders JSON" do
article = Article.create!(title: "Hello, world")
render formats: :json, partial: "articles/article", locals: { article: article }
assert_pattern { rendered_json => { title: "Hello, world" } }
end
要将渲染的内容解析为 RSS,请注册对 RSS::Parser.parse 的调用
register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }
test "renders RSS" do
article = Article.create!(title: "Hello, world")
render formats: :rss, partial: article, locals: { article: article }
assert_equal "Hello, world", rendered_rss.items.last.title
end
要将渲染的内容解析为 Capybara::Simple::Node,请使用对 Capybara.string 的调用重新注册 :html 解析器
register_parser :html, -> rendered { Capybara.string(rendered) }
test "renders HTML" do
article = Article.create!(title: "Hello, world")
render partial: article
rendered_html.assert_css "main h1", text: "Hello, world"
end
3. Railties
请参阅更改日志了解详细更改。
3.1. 移除
移除已弃用的
bin/rails secrets:setup命令。移除默认的
X-Download-Options标头,因为它仅由 Internet Explorer 使用。
3.2. 弃用
弃用
Rails.application.secrets的用法。弃用
secrets:show和secrets:edit命令,转而使用credentials。弃用
Rails::Generators::Testing::Behaviour,转而使用Rails::Generators::Testing::Behavior。
3.3. 显著变更
添加
sandbox_by_default选项,以默认在沙盒模式下启动 rails 控制台。添加用于支持按行范围过滤测试的新语法。
添加
DATABASE选项,允许在执行rails railties:install:migrations命令以复制迁移时指定目标数据库。在
rails new --javascript生成器中添加对 Bun 的支持。$ rails new my_new_app --javascript=bun添加在测试运行器中显示慢速测试的功能。
4. Action Cable
请参阅更改日志了解详细更改。
4.1. 移除
4.2. 弃用
4.3. 显著变更
添加
capture_broadcasts测试助手,以捕获块中广播的所有消息。添加 Redis 发布/订阅适配器在 Redis 连接丢失时自动重新连接的功能。
在
ActionCable::Connection::Base中添加命令回调before_command、after_command和around_command。
5. Action Pack
请参阅更改日志了解详细更改。
5.1. 移除
移除
Request#content_type上已弃用的行为移除已弃用的一次性将单个值分配给
config.action_dispatch.trusted_proxies的功能。移除已弃用的
poltergeist和webkit(capybara-webkit) 驱动程序注册,用于系统测试。
5.2. 弃用
弃用
config.action_dispatch.return_only_request_media_type_on_content_type。弃用
AbstractController::Helpers::MissingHelperError。弃用
ActionDispatch::IllegalStateError。弃用
speaker、vibrate和vr权限策略指令。弃用
config.action_dispatch.show_exceptions的true和false值,转而使用:all、:rescuable或:none。
5.3. 显著变更
向
ActionController::Parameters添加exclude?方法。它是include?方法的反向。添加
ActionController::Parameters#extract_value方法,以允许从参数中提取序列化值。添加使用自定义逻辑存储和检索 CSRF 令牌的功能。
为系统测试截图助手添加
html和screenshot关键字参数。
6. Action View
请参阅更改日志了解详细更改。
6.1. 移除
移除已弃用的常量
ActionView::Path。移除已弃用的支持,即传递实例变量作为局部变量给局部视图。
6.2. 弃用
6.3. 显著变更
checkbox_tag和radio_button_tag现在接受checked作为关键字参数。添加
picture_tag助手以生成 HTML<picture>标签。simple_format助手现在支持:sanitize_options功能,允许为清理过程添加额外选项。simple_format("<a target=\"_blank\" href=\"http://example.com\">Continue</a>", {}, { sanitize_options: { attributes: %w[target href] } }) # => "<p><a target=\"_blank\" href=\"http://example.com\">Continue</a></p>"
7. Action Mailer
请参阅更改日志了解详细更改。
7.1. 移除
7.2. 弃用
弃用
config.action_mailer.preview_path。弃用通过
:args关键字参数向assert_enqueued_email_with传递参数。现在支持:params关键字参数,因此请使用它来传递参数。
7.3. 显著变更
添加
config.action_mailer.preview_paths以支持多个预览路径。在测试助手中添加
capture_emails以捕获块中发送的所有电子邮件。在
ActionMailer::TestHelper中添加deliver_enqueued_emails以传递所有已入队的电子邮件作业。
8. Active Record
请参阅更改日志了解详细更改。
8.1. 移除
移除对
ActiveRecord.legacy_connection_handling的支持。移除已弃用的
ActiveRecord::Base配置访问器移除对
configs_for上的:include_replicas的支持。请改用:include_hidden。移除已弃用的
config.active_record.partial_writes。移除已弃用的
Tasks::DatabaseTasks.schema_file_type。移除 PostgreSQL 结构转储中的
--no-comments标志。
8.2. 弃用
弃用
#remove_connection上的name参数。弃用
check_pending!,转而使用check_all_pending!。弃用
add_foreign_key的deferrable: true选项,转而使用deferrable: :immediate。弃用
TestFixtures#fixture_path,转而使用TestFixtures#fixture_paths。弃用从
Base到connection_handler的委托。弃用
config.active_record.suppress_multiple_database_warning。弃用在 SQL 字符串模板中使用
ActiveSupport::Duration作为内插绑定参数。弃用
all_connection_pools并使connection_pool_list更明确。弃用
read_attribute(:id)返回主键(如果主键不是:id)。弃用
#merge上的rewhere参数。弃用使用
alias_attribute别名非属性。
8.3. 显著变更
添加
TestFixtures#fixture_paths以支持多个 fixture 路径。使用
has_secure_password时添加authenticate_by。在
ActiveRecord::Persistence中添加update_attribute!,它类似于update_attribute,但在before_*回调抛出:abort时会引发ActiveRecord::RecordNotSaved。允许将别名属性与
insert_all/upsert_all一起使用。添加
:include选项到add_index。添加
#regroup查询方法,作为.unscope(:group).group(fields)的简写。为
SQLite3适配器添加了对自动填充列和自定义主键的支持。为
SQLite3数据库连接添加了现代化、高性能的默认值。允许使用列元组语法指定 where 子句。
Topic.where([:title, :author_name] => [["The Alchemist", "Paulo Coelho"], ["Harry Potter", "J.K Rowling"]])自动生成的索引名称现在限制为 62 字节,符合 MySQL、PostgreSQL 和 SQLite 的默认索引名称长度限制。
引入 Trilogy 数据库客户端的适配器。
添加
ActiveRecord.disconnect_all!方法,立即关闭所有连接池中的所有连接。添加 PostgreSQL 迁移命令,用于枚举重命名、添加值和重命名值。
添加
ActiveRecord::Base#id_value别名以访问记录 ID 列的原始值。添加
enum的验证选项。用于
ActiveRecord::Encryption的默认哈希摘要(用于使用encrypts定义的属性)现在是SHA256,从默认配置中的SHA1更改。这些默认值还包括support_sha1_for_non_deterministic_encryption = false,如果数据未重新加密,这可能导致应用程序无法解密使用旧默认哈希摘要加密的数据。
9. Active Storage
请参阅更改日志了解详细更改。
9.1. 移除
移除 Active Storage 配置中已弃用的无效默认内容类型。
移除已弃用的
ActiveStorage::Current#host和ActiveStorage::Current#host=方法。移除在分配给附件集合时已弃用的行为。现在集合被替换,而不是添加到集合中。
移除附件关联中已弃用的
purge和purge_later方法。
9.2. 弃用
9.3. 显著变更
ActiveStorage::Analyzer::AudioAnalyzer现在在输出metadata哈希中输出sample_rate和tags。添加了在附件上调用
preview或representation方法时利用预定义变体的选项。声明变体时添加了
preprocessed选项以预处理变体。添加了销毁 Active Storage 变体的功能。
User.first.avatar.variant(resize_to_limit: [100, 100]).destroy
10. Active Model
请参阅更改日志了解详细更改。
10.1. 移除
10.2. 弃用
10.3. 显著变更
添加了对
LengthValidator的:in/:within选项的无限范围支持。validates_length_of :first_name, in: ..30添加了对
inclusivity/exclusivity验证器的无始范围支持。validates_inclusion_of :birth_date, in: -> { (..Date.today) }validates_exclusion_of :birth_date, in: -> { (..Date.today) }添加了对
has_secure_password的密码挑战支持。设置后,验证密码挑战是否与持久化的password_digest匹配。允许验证器接受不带记录参数的 lambda。
# Before validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today } # After validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
11. Active Support
请参阅更改日志了解详细更改。
11.1. 移除
移除已弃用的
Enumerable#sum覆盖。移除已弃用的
ActiveSupport::PerThreadRegistry。移除已弃用的选项,即在
Array、Range、Date、DateTime、Time、BigDecimal、Float和Integer中将格式传递给#to_s。移除已弃用的
ActiveSupport::TimeWithZone.name覆盖。移除已弃用的
active_support/core_ext/uri文件。移除已弃用的
active_support/core_ext/range/include_time_with_zone文件。移除
ActiveSupport::SafeBuffer将对象隐式转换为String的行为。移除已弃用的支持,即在提供不是
Digest::UUID上定义的常量之一的命名空间 ID 时生成不正确的 RFC 4122 UUID。
11.2. 弃用
弃用
config.active_support.disable_to_s_conversion。弃用
config.active_support.remove_deprecated_time_with_zone_name。弃用
config.active_support.use_rfc4122_namespaced_uuids。弃用
SafeBuffer#clone_empty。弃用单例
ActiveSupport::Deprecation的用法。弃用使用
Dalli::Client实例初始化ActiveSupport::Cache::MemCacheStore。弃用
Notification::Event的#children和#parent_of?方法。
11.3. 显著变更
12. Active Job
请参阅更改日志了解详细更改。
12.1. 移除
- 移除
QueAdapter。
12.2. 弃用
12.3. 显著变更
添加
perform_all_later以一次性将多个作业入队。为作业生成器添加
--parent选项以指定作业的父类。向
ActiveJob::Base添加after_discard方法,以便在作业即将被丢弃时运行回调。添加通过
config.active_job.verbose_enqueue_logs记录后台作业入队调用者的支持。
13. Action Text
请参阅更改日志了解详细更改。
13.1. 移除
13.2. 弃用
13.3. 显著变更
14. Action Mailbox
请参阅更改日志了解详细更改。
14.1. 移除
14.2. 弃用
14.3. 显著变更
将
X-Forwarded-To地址添加到收件人。向
ActionMailbox::Base添加bounce_now_with方法,以便直接发送退回邮件,无需经过邮件队列。
15. Ruby on Rails 指南
请参阅更改日志了解详细更改。
15.1. 显著变更
16. 致谢
请参阅Rails 贡献者完整列表,感谢许多人花费大量时间使 Rails 成为一个稳定而健壮的框架。向他们所有人致敬。