更多内容请访问 rubyonrails.org:

升级 Ruby on Rails

本指南提供了将应用程序升级到更新版本的 Ruby on Rails 时应遵循的步骤。这些步骤也可以在单独的版本指南中找到。

1 一般建议

在尝试升级现有应用程序之前,您应该确保您有充分的理由进行升级。您需要权衡几个因素:对新功能的需求,对旧代码的支持越来越困难,以及您的可用时间和技能等等。

1.1 测试覆盖率

确保应用程序在升级后仍能正常运行的最佳方法是在开始升级之前具有良好的测试覆盖率。如果您没有运行应用程序大部分的自动化测试,则需要花费时间手动运行所有已更改的部分。对于 Rails 升级,这意味着应用程序中的每个功能。给自己一个方便,确保您的测试覆盖率在开始升级之前是良好的。

1.2 Ruby 版本

Rails 通常在发布时会紧跟最新发布的 Ruby 版本。

  • Rails 8.0 需要 Ruby 3.2.0 或更高版本。
  • Rails 7.2 需要 Ruby 3.1.0 或更高版本。
  • Rails 7.0 和 7.1 需要 Ruby 2.7.0 或更高版本。
  • Rails 6 需要 Ruby 2.5.0 或更高版本。
  • Rails 5 需要 Ruby 2.2.2 或更高版本。

最好分别升级 Ruby 和 Rails。先升级到您能使用的最新 Ruby 版本,然后再升级 Rails。

1.3 升级流程

更改 Rails 版本时,最好一次逐步进行,每次升级一个小版本,以便充分利用弃用警告。Rails 版本号采用 Major.Minor.Patch 的形式。主要版本和次要版本允许对公共 API 进行更改,因此这可能会导致您的应用程序出现错误。补丁版本仅包含错误修复,不会更改任何公共 API。

流程应如下所示

  1. 编写测试并确保它们通过。
  2. 移动到当前版本之后的最新补丁版本。
  3. 修复测试和弃用功能。
  4. 移动到下一个次要版本的最新补丁版本。

重复此过程,直到达到目标 Rails 版本。

1.3.1 在版本之间移动

要在版本之间移动

  1. 更改 Gemfile 中的 Rails 版本号并运行 bundle update rails
  2. 更改 package.json 中 Rails JavaScript 包的版本,如果运行 jsbundling-rails,则运行 bin/rails javascript:install
  3. 运行 更新任务.
  4. 运行您的测试。

您可以在 此处找到所有已发布的 Rails gem 的列表。

1.4 更新任务

Rails 提供了 rails app:update 命令。在 Gemfile 中更新 Rails 版本后,运行此命令。这将帮助您在交互式会话中创建新文件和更改旧文件。

$ bin/rails app:update
       exist  config
    conflict  config/application.rb
Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
       force  config/application.rb
      create  config/initializers/new_framework_defaults_8_0.rb
...

不要忘记查看差异,以查看是否存在任何意外更改,并注意在此过程中使用的 diff 和合并工具可以通过 THOR_DIFFTHOR_MERGE 环境变量定义。

1.5 配置框架默认值

新的 Rails 版本可能具有与先前版本不同的配置默认值。但是,在完成上述步骤之后,您的应用程序仍然可以使用先前 Rails 版本的配置默认值运行。这是因为 config/application.rbconfig.load_defaults 的值尚未更改。

为了让您能够逐步升级到新的默认值,更新任务创建了一个文件 config/initializers/new_framework_defaults_X.Y.rb(文件名中包含所需的 Rails 版本)。您应该通过取消注释文件中的新配置默认值来启用它们;这可以在多个部署中逐步完成。当您的应用程序准备好使用新的默认值运行时,您可以删除此文件并翻转 config.load_defaults 值。

2 从 Rails 7.2 升级到 Rails 8.0

有关对 Rails 8.0 所做的更改的更多信息,请参阅 发行说明.

3 从 Rails 7.1 升级到 Rails 7.2

有关对 Rails 7.2 所做的更改的更多信息,请参阅 发行说明.

3.1 所有测试现在都尊重 active_job.queue_adapter 配置

如果您在 config/application.rbconfig/environments/test.rb 文件中设置了 config.active_job.queue_adapter,则您选择的适配器之前未在所有测试中一致地使用。在某些测试中,您的适配器会被使用,但其他测试会使用 TestAdapter

在 Rails 7.2 中,所有测试都将尊重 queue_adapter 配置(如果提供)。如果您将 queue_adapter 配置设置为除 :test 之外的其他内容,但以依赖于 TestAdapter 的方式编写测试,这可能会导致测试错误。

如果没有提供任何配置,则将继续使用 TestAdapter

4 从 Rails 7.0 升级到 Rails 7.1

有关对 Rails 7.1 所做的更改的更多信息,请参阅 发行说明.

4.1 开发和测试环境的 secret_key_base 文件已更改

在开发和测试环境中,Rails 从中读取 secret_key_base 的文件已从 tmp/development_secret.txt 重命名为 tmp/local_secret.txt

您可以简单地将先前文件重命名为 local_secret.txt 以继续使用相同的密钥,或者将密钥从先前文件复制到新文件。

如果这样做,Rails 将在应用程序加载时在新文件 tmp/local_secret.txt 中生成一个新的密钥。

这将使开发和测试环境中所有现有的会话/cookie 无效,并且还会导致从 secret_key_base 派生的其他签名(如 Active Storage/Action Text 附件)失效。

生产和其他环境不受影响。

4.2 自动加载路径不再在 $LOAD_PATH 中

从 Rails 7.1 开始,自动加载器管理的目录不再添加到 $LOAD_PATH 中。这意味着无法使用手动 require 调用加载它们的文件,无论如何都不应该这样做。

减小 $LOAD_PATH 的大小会加快不使用 bootsnap 的应用程序的 require 调用速度,并减小其他应用程序的 bootsnap 缓存的大小。

如果您希望这些路径仍然在 $LOAD_PATH 中,您可以选择加入

config.add_autoload_paths_to_load_path = true

但我们不鼓励这样做,自动加载路径中的类和模块应该自动加载。也就是说,只需引用它们。

lib 目录不受此标志的影响,它始终添加到 $LOAD_PATH 中。

4.3 config.autoload_lib 和 config.autoload_lib_once

如果您的应用程序没有在自动加载或自动加载一次路径中包含 lib,请跳过本节。您可以通过检查以下输出来确定这一点

# Print autoload paths.
$ bin/rails runner 'pp Rails.autoloaders.main.dirs'

# Print autoload once paths.
$ bin/rails runner 'pp Rails.autoloaders.once.dirs'

如果您的应用程序已经在自动加载路径中包含 lib,通常在 config/application.rb 中会有类似于以下内容的配置

# Autoload lib, but do not eager load it (maybe overlooked).
config.autoload_paths << config.root.join("lib")

# Autoload and also eager load lib.
config.autoload_paths << config.root.join("lib")
config.eager_load_paths << config.root.join("lib")

# Same, because all eager load paths become autoload paths too.
config.eager_load_paths << config.root.join("lib")

这仍然有效,但建议将这些行替换为更简洁的

config.autoload_lib(ignore: %w(assets tasks))

请将任何其他不包含 .rb 文件或不应该重新加载或急切加载的 lib 子目录添加到 ignore 列表中。例如,如果您的应用程序具有 lib/templateslib/generatorslib/middleware,则将它们相对于 lib 的名称添加到

config.autoload_lib(ignore: %w(assets tasks templates generators middleware))

使用这一行代码,lib 中的(非忽略的)代码如果 config.eager_loadtrueproduction 模式下的默认值),也将被急切加载。这通常是您想要的,但是如果 lib 之前没有添加到急切加载路径中,并且您仍然希望保持这种方式,请选择退出

Rails.autoloaders.main.do_not_eager_load(config.root.join("lib"))

config.autoload_lib_once 方法是类似的方法,如果应用程序在 config.autoload_once_paths 中包含 lib

4.4 ActiveStorage::BaseController 不再包含流式传输关注项

继承自 ActiveStorage::BaseController 并使用流式传输来实现自定义文件服务逻辑的应用程序控制器现在必须显式包含 ActiveStorage::Streaming 模块。

4.5 MemCacheStoreRedisCacheStore 现在默认使用连接池

connection_pool gem 已作为 activesupport gem 的依赖项添加,MemCacheStoreRedisCacheStore 现在默认使用连接池。

如果您不想使用连接池,请在配置缓存存储时将 :pool 选项设置为 false

config.cache_store = :mem_cache_store, "cache.example.com", { pool: false }

有关更多信息,请参阅 使用 Rails 缓存 指南。

4.6 SQLite3Adapter 现在配置为在严格的字符串模式下使用

使用严格的字符串模式会禁用双引号字符串文字。

SQLite 在双引号字符串文字方面存在一些怪癖。它首先尝试将双引号字符串视为标识符名称,但如果它们不存在,则将其视为字符串文字。因此,错别字可能会被默默地忽略。例如,可以为不存在的列创建索引。有关更多详细信息,请参阅 SQLite 文档

如果您不想在严格模式下使用 SQLite3Adapter,可以禁用此行为

# config/application.rb
config.active_record.sqlite3_adapter_strict_strings_by_default = false

4.7 支持 ActionMailer::Preview 的多个预览路径

选项 config.action_mailer.preview_path 已弃用,取而代之的是 config.action_mailer.preview_paths。将路径追加到此配置选项将导致在搜索邮件预览时使用这些路径。

config.action_mailer.preview_paths << "#{Rails.root}/lib/mailer_previews"

4.8 config.i18n.raise_on_missing_translations = true 现在会针对任何缺少的翻译引发异常。

以前,它只会在视图或控制器中被调用时引发异常。现在,只要 I18n.t 提供了一个无法识别的键,它就会引发异常。

# with config.i18n.raise_on_missing_translations = true

# in a view or controller:
t("missing.key") # raises in 7.0, raises in 7.1
I18n.t("missing.key") # didn't raise in 7.0, raises in 7.1

# anywhere:
I18n.t("missing.key") # didn't raise in 7.0, raises in 7.1

如果您不希望这种行为,可以设置 config.i18n.raise_on_missing_translations = false

# with config.i18n.raise_on_missing_translations = false

# in a view or controller:
t("missing.key") # didn't raise in 7.0, doesn't raise in 7.1
I18n.t("missing.key") # didn't raise in 7.0, doesn't raise in 7.1

# anywhere:
I18n.t("missing.key") # didn't raise in 7.0, doesn't raise in 7.1

或者,您可以自定义 I18n.exception_handler。有关更多信息,请参阅 i18n 指南

AbstractController::Translation.raise_on_missing_translations 已被移除。这是一个私有 API,如果您依赖它,应该迁移到 config.i18n.raise_on_missing_translations 或自定义异常处理程序。

4.9 bin/rails test 现在运行 test:prepare 任务

通过 bin/rails test 运行测试时,rake test:prepare 任务将在测试运行之前运行。如果您增强了 test:prepare 任务,您的增强功能将在您的测试之前运行。tailwindcss-railsjsbundling-railscssbundling-rails 以及其他第三方 gem 增强了此任务。

有关更多信息,请参阅 测试 Rails 应用程序 指南。

如果您运行单个文件的测试(bin/rails test test/models/user_test.rb),test:prepare 将不会在其之前运行。

4.10 @rails/ujs 中的导入语法已修改

从 Rails 7.1 开始,从 @rails/ujs 导入模块的语法已修改。Rails 不再支持直接从 @rails/ujs 导入模块。

例如,尝试从库中导入函数将失败

import { fileInputSelector } from "@rails/ujs"
// ERROR: export 'fileInputSelector' (imported as 'fileInputSelector') was not found in '@rails/ujs' (possible exports: default)

在 Rails 7.1 中,用户应首先直接从 @rails/ujs 导入 Rails 对象。然后,用户可以从 Rails 对象导入特定模块。

以下是 Rails 7.1 中导入的示例

import Rails from "@rails/ujs"
// Alias the method
const fileInputSelector = Rails.fileInputSelector
// Alternatively, reference it from the Rails object where it is used
Rails.fileInputSelector(...)

4.11 Rails.logger 现在返回 ActiveSupport::BroadcastLogger 实例

ActiveSupport::BroadcastLogger 类是一个新的记录器,它允许以简单的方式将记录广播到不同的接收器(STDOUT、日志文件...)。

广播日志的 API(使用 ActiveSupport::Logger.broadcast 方法)已被移除,并且以前是私有的。如果您的应用程序或库依赖此 API,您需要进行以下更改

logger = Logger.new("some_file.log")

# Before

Rails.logger.extend(ActiveSupport::Logger.broadcast(logger))

# After

Rails.logger.broadcast_to(logger)

如果您的应用程序配置了自定义日志记录器,则Rails.logger将包装并代理所有方法到该日志记录器。您无需进行任何更改即可使其工作。

如果您需要访问自定义日志记录器实例,可以使用broadcasts方法。

# config/application.rb
config.logger = MyLogger.new

# Anywhere in your application
puts Rails.logger.class #=> BroadcastLogger
puts Rails.logger.broadcasts #=> [MyLogger]

4.12 Active Record 加密算法更改

Active Record 加密现在使用 SHA-256 作为其哈希摘要算法。如果您有使用先前 Rails 版本加密的数据,则需要考虑两种情况。

  1. 如果您将config.active_support.key_generator_hash_digest_class配置为 SHA-1(Rails 7.0 之前的默认值),则还需要为 Active Record 加密配置 SHA-1。

    config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA1
    
  2. 如果您将config.active_support.key_generator_hash_digest_class配置为 SHA-256(7.0 中的新默认值),则需要为 Active Record 加密配置 SHA-256。

    config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256
    

有关config.active_record.encryption.hash_digest_class的更多信息,请参阅配置 Rails 应用程序指南。

此外,还引入了一个新的配置config.active_record.encryption.support_sha1_for_non_deterministic_encryption,以解决一个错误,该错误导致某些属性即使在通过上述hash_digest_class配置配置了 SHA-256 时也使用 SHA-1 进行加密。

默认情况下,在 Rails 7.1 中禁用config.active_record.encryption.support_sha1_for_non_deterministic_encryption。如果您有在 Rails < 7.1 版本中加密的数据,并且您认为可能受上述错误影响,则应启用此配置。

config.active_record.encryption.support_sha1_for_non_deterministic_encryption = true

如果您正在处理加密数据,请仔细查看上述内容。

4.13 在控制器测试、集成测试和系统测试中处理异常的新方法

config.action_dispatch.show_exceptions配置控制 Action Pack 如何处理在响应请求时引发的异常。

在 Rails 7.1 之前,将config.action_dispatch.show_exceptions = true设置为 true 会将 Action Pack 配置为捕获异常并呈现相应的 HTML 错误页面,例如使用 404 Not found 状态代码呈现public/404.html,而不是引发ActiveRecord::RecordNotFound异常。将config.action_dispatch.show_exceptions = false设置为 false 会将 Action Pack 配置为不捕获异常。在 Rails 7.1 之前,新的应用程序是在config/environments/test.rb中生成一行代码,该代码将config.action_dispatch.show_exceptions设置为 false。

Rails 7.1 将可接受的值从truefalse更改为:all:rescuable:none

  • :all - 为所有异常呈现 HTML 错误页面(等同于true
  • :rescuable - 为由config.action_dispatch.rescue_responses声明的异常呈现 HTML 错误页面
  • :none(等同于false) - 不捕获任何异常

由 Rails 7.1 或更高版本生成的应用程序在config/environments/test.rb中将config.action_dispatch.show_exceptions设置为:rescuable。在升级时,现有应用程序可以将config.action_dispatch.show_exceptions更改为:rescuable以利用新行为,或者用相应的新的值替换旧的值(true替换:allfalse替换:none)。

5 从 Rails 6.1 升级到 Rails 7.0

有关对 Rails 7.0 进行的更改的更多信息,请参阅发布说明

5.1 ActionView::Helpers::UrlHelper#button_to行为更改

从 Rails 7.0 开始,如果使用持久化的 Active Record 对象来构建按钮 URL,button_to将呈现带有patch HTTP 动词的form标签。要保持当前行为,请考虑显式传递method:选项

-button_to("Do a POST", [:my_custom_post_action_on_workshop, Workshop.find(1)])
+button_to("Do a POST", [:my_custom_post_action_on_workshop, Workshop.find(1)], method: :post)

或使用辅助函数来构建 URL

-button_to("Do a POST", [:my_custom_post_action_on_workshop, Workshop.find(1)])
+button_to("Do a POST", my_custom_post_action_on_workshop_workshop_path(Workshop.find(1)))

5.2 Spring

如果您的应用程序使用 Spring,则需要将其升级到至少 3.0.0 版本。否则您将收到

undefined method `mechanism=' for ActiveSupport::Dependencies:Module

此外,请确保config.cache_classesconfig/environments/test.rb中设置为false

5.3 Sprockets 现在是可选依赖项

gem rails不再依赖于sprockets-rails。如果您的应用程序仍然需要使用 Sprockets,请确保将sprockets-rails添加到您的 Gemfile 中。

gem "sprockets-rails"

5.4 应用程序需要在zeitwerk模式下运行

仍然在classic模式下运行的应用程序必须切换到zeitwerk模式。有关详细信息,请查看从 Classic 切换到 Zeitwerk 的方法指南。

5.5 setter config.autoloader= 已删除

在 Rails 7 中,没有配置点可以设置自动加载模式,config.autoloader=已删除。如果您出于任何原因将其设置为:zeitwerk,只需将其删除即可。

5.6 ActiveSupport::Dependencies 私有 API 已删除

ActiveSupport::Dependencies的私有 API 已删除。这包括hook!unhook!depend_onrequire_or_loadmechanism以及许多其他方法。

一些重点

  • 如果您使用的是ActiveSupport::Dependencies.constantizeActiveSupport::Dependencies.safe_constantize,只需将其更改为String#constantizeString#safe_constantize即可。
  ActiveSupport::Dependencies.constantize("User") # NO LONGER POSSIBLE
  "User".constantize # 👍
  • 任何对ActiveSupport::Dependencies.mechanism的使用,无论是读取还是写入,都必须通过相应地访问config.cache_classes来替换。

  • 如果您想跟踪自动加载器的活动,ActiveSupport::Dependencies.verbose=已不再可用,只需在config/application.rb中抛出Rails.autoloaders.log!

辅助内部类或模块也消失了,例如ActiveSupport::Dependencies::ReferenceActiveSupport::Dependencies::Blamable等。

5.7 初始化期间的自动加载

在初始化期间在to_prepare块之外自动加载可重新加载的常量的应用程序,会将这些常量卸载并发出此警告,因为 Rails 6.0 开始了这个操作。

DEPRECATION WARNING: Initialization autoloaded the constant ....

Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.

...

如果您在日志中仍然收到此警告,请查看自动加载指南中关于应用程序启动时自动加载的部分。否则,您将在 Rails 7 中收到NameError

once自动加载器管理的常量可以在初始化期间自动加载,并且可以正常使用,无需to_prepare块。但是,once自动加载器现在设置得更早,以支持这一点。如果应用程序具有自定义词形变化,并且once自动加载器应该知道这些词形变化,则需要将config/initializers/inflections.rb中的代码移动到config/application.rb中应用程序类定义的主体中。

module MyApp
  class Application < Rails::Application
    # ...

    ActiveSupport::Inflector.inflections(:en) do |inflect|
      inflect.acronym "HTML"
    end
  end
end

5.8 能够配置config.autoload_once_paths

config.autoload_once_paths可以在config/application.rb中定义的应用程序类的主体中或在config/environments/*中环境的配置中设置。

类似地,引擎可以在引擎类的类主体中或环境的配置中配置该集合。

之后,集合被冻结,您可以从这些路径自动加载。特别是,您可以在初始化期间从那里自动加载。它们由Rails.autoloaders.once自动加载器管理,该自动加载器不重新加载,只自动加载/急切加载。

如果您在环境配置处理完后配置了此设置,并且收到FrozenError,请将代码移至适当位置即可。

5.9 ActionDispatch::Request#content_type现在按原样返回 Content-Type 标头。

以前,ActionDispatch::Request#content_type返回的值不包含字符集部分。此行为已更改为返回包含字符集部分的 Content-Type 标头,按原样返回。

如果您只需要 MIME 类型,请改用ActionDispatch::Request#media_type

之前

request = ActionDispatch::Request.new("CONTENT_TYPE" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv"

之后

request = ActionDispatch::Request.new("Content-Type" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv; header=present; charset=utf-16"
request.media_type   #=> "text/csv"

密钥生成器的默认摘要类正在从 SHA1 更改为 SHA256。这对 Rails 生成的任何加密消息(包括加密的 cookie)都有影响。

为了能够使用旧的摘要类读取消息,有必要注册一个旋转器。如果未这样做,可能会导致用户在升级过程中会话失效。

以下是如何为加密和已签名的 cookie 注册旋转器的示例。

# config/initializers/cookie_rotator.rb
Rails.application.config.after_initialize do
  Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
    authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
    signed_cookie_salt = Rails.application.config.action_dispatch.signed_cookie_salt

    secret_key_base = Rails.application.secret_key_base

    key_generator = ActiveSupport::KeyGenerator.new(
      secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1
    )
    key_len = ActiveSupport::MessageEncryptor.key_len

    old_encrypted_secret = key_generator.generate_key(authenticated_encrypted_cookie_salt, key_len)
    old_signed_secret = key_generator.generate_key(signed_cookie_salt)

    cookies.rotate :encrypted, old_encrypted_secret
    cookies.rotate :signed, old_signed_secret
  end
end

5.11 ActiveSupport::Digest 的摘要类更改为 SHA256

ActiveSupport::Digest 的默认摘要类正在从 SHA1 更改为 SHA256。这对诸如 Etags 等内容有影响,这些内容将发生更改,缓存键也会发生更改。更改这些键会影响缓存命中率,因此在升级到新的哈希时要小心并注意这一点。

5.12 新的 ActiveSupport::Cache 序列化格式

引入了更快、更紧凑的序列化格式。

要启用它,您必须设置config.active_support.cache_format_version = 7.0

# config/application.rb

config.load_defaults 6.1
config.active_support.cache_format_version = 7.0

或者只需

# config/application.rb

config.load_defaults 7.0

但是,Rails 6.1 应用程序无法读取这种新的序列化格式,因此,为了确保顺利升级,您必须首先使用config.active_support.cache_format_version = 6.1部署 Rails 7.0 升级,然后只有在所有 Rails 进程都更新后,才能设置config.active_support.cache_format_version = 7.0

Rails 7.0 能够读取这两种格式,因此缓存不会在升级期间失效。

5.13 Active Storage 视频预览图像生成

视频预览图像生成现在使用 FFmpeg 的场景变化检测来生成更有意义的预览图像。以前,会使用视频的第一帧,如果视频从黑色淡入,就会造成问题。此更改需要 FFmpeg v3.4+。

5.14 Active Storage 默认变体处理器已更改为:vips

对于新的应用程序,图像转换将使用 libvips 而不是 ImageMagick。这将减少生成变体所需的时间以及 CPU 和内存使用量,从而改善依赖 Active Storage 来提供图像的应用程序的响应时间。

:mini_magick选项没有被弃用,因此可以继续使用它。

要将现有应用程序迁移到 libvips,请设置

Rails.application.config.active_storage.variant_processor = :vips

然后,您需要将现有的图像转换代码更改为image_processing宏,并将 ImageMagick 的选项替换为 libvips 的选项。

5.14.1 将 resize 替换为 resize_to_limit

- variant(resize: "100x")
+ variant(resize_to_limit: [100, nil])

如果不这样做,切换到 vips 时,您会看到以下错误:no implicit conversion to float from string

5.14.2 在裁剪时使用数组

- variant(crop: "1920x1080+0+0")
+ variant(crop: [0, 0, 1920, 1080])

如果在迁移到 vips 时不这样做,您会看到以下错误:unable to call crop: you supplied 2 arguments, but operation needs 5

5.14.3 限制您的裁剪值

在裁剪方面,Vips 比 ImageMagick 更严格

  1. 如果x和/或y为负值,它将不会裁剪。例如:[-10, -10, 100, 100]
  2. 如果位置(xy)加上裁剪尺寸(widthheight)大于图像,它将不会裁剪。例如:125x125 的图像和[50, 50, 100, 100]的裁剪

如果在迁移到 vips 时不这样做,您会看到以下错误:extract_area: bad extract area

5.14.4 调整用于resize_and_pad的背景颜色

Vips 使用黑色作为resize_and_pad的默认背景颜色,而不是像 ImageMagick 那样使用白色。通过使用background选项来解决此问题

- variant(resize_and_pad: [300, 300])
+ variant(resize_and_pad: [300, 300, background: [255]])

5.14.5 删除任何基于 EXIF 的旋转

Vips 在处理变体时会自动使用 EXIF 值旋转图像。如果您以前将用户上传的照片的旋转值存储起来以使用 ImageMagick 应用旋转,则必须停止这样做

- variant(format: :jpg, rotate: rotation_value)
+ variant(format: :jpg)

5.14.6 将 monochrome 替换为 colourspace

Vips 使用不同的选项来制作单色图像

- variant(monochrome: true)
+ variant(colourspace: "b-w")

5.14.7 切换到 libvips 选项来压缩图像

JPEG

- variant(strip: true, quality: 80, interlace: "JPEG", sampling_factor: "4:2:0", colorspace: "sRGB")
+ variant(saver: { strip: true, quality: 80, interlace: true })

PNG

- variant(strip: true, quality: 75)
+ variant(saver: { strip: true, compression: 9 })

WEBP

- variant(strip: true, quality: 75, define: { webp: { lossless: false, alpha_quality: 85, thread_level: 1 } })
+ variant(saver: { strip: true, quality: 75, lossless: false, alpha_q: 85, reduction_effort: 6, smart_subsample: true })

GIF

- variant(layers: "Optimize")
+ variant(saver: { optimize_gif_frames: true, optimize_gif_transparency: true })

5.14.8 部署到生产环境

Active Storage 在图像 URL 中编码要执行的转换列表。如果您的应用程序正在缓存这些 URL,则在将新代码部署到生产环境后,您的图像将无法正常显示。因此,您必须手动使受影响的缓存键失效。

例如,如果您的视图中有以下代码:

<% @products.each do |product| %>
  <% cache product do %>
    <%= image_tag product.cover_photo.variant(resize: "200x") %>
  <% end %>
<% end %>

您可以通过更新产品或更改缓存键来使缓存失效。

<% @products.each do |product| %>
  <% cache ["v2", product] do %>
    <%= image_tag product.cover_photo.variant(resize_to_limit: [200, nil]) %>
  <% end %>
<% end %>

5.15 Rails 版本现在包含在 Active Record 架构转储中。

Rails 7.0 更改了一些列类型默认值。为了避免从 6.1 升级到 7.0 的应用程序使用新的 7.0 默认值加载当前架构,Rails 现在将框架版本包含在架构转储中。

在 Rails 7.0 中首次加载架构之前,请确保运行 rails app:update 以确保架构转储中包含架构版本。

架构文件将如下所示:

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[6.1].define(version: 2022_01_28_123512) do
  # ...
end

您第一次使用 Rails 7.0 转储架构时,将看到该文件中许多更改,包括一些列信息。请务必查看新架构文件内容并将其提交到您的仓库。

6 从 Rails 6.0 升级到 Rails 6.1

有关对 Rails 6.1 进行的更改的更多信息,请参阅 发行说明

6.1 Rails.application.config_for 的返回值不再支持使用字符串键访问。

给定一个像这样的配置文件:

# config/example.yml
development:
  options:
    key: value
Rails.application.config_for(:example).options

它过去会返回一个哈希,您可以在其中使用字符串键访问值。这在 6.0 中已被弃用,现在不再起作用。

如果仍然想使用字符串键访问值,可以在 config_for 的返回值上调用 with_indifferent_access,例如:

Rails.application.config_for(:example).with_indifferent_access.dig("options", "key")

6.2 使用 respond_to#any 时的响应 Content-Type

响应中返回的 Content-Type 标头可能与 Rails 6.0 返回的值不同,特别是如果您的应用程序使用 respond_to { |format| format.any }。Content-Type 现在将基于给定块而不是请求的格式。

示例

def my_action
  respond_to do |format|
    format.any { render(json: { foo: "bar" }) }
  end
end
get("my_action.csv")

以前的行为是返回一个 text/csv 响应的 Content-Type,这并不准确,因为正在渲染 JSON 响应。当前行为正确地返回了 application/json 响应的 Content-Type。

如果您的应用程序依赖于以前不正确的行为,建议您指定操作接受的格式,即:

format.any(:xml, :json) { render request.format.to_sym => @people }

6.3 ActiveSupport::Callbacks#halted_callback_hook 现在接收第二个参数

Active Support 允许您在回调中断链时覆盖 halted_callback_hook。此方法现在接收第二个参数,即正在中断的回调的名称。如果您的类覆盖了此方法,请确保它接受两个参数。请注意,这是一个没有经过事先弃用周期的重大更改(出于性能原因)。

示例

class Book < ApplicationRecord
  before_save { throw(:abort) }
  before_create { throw(:abort) }

  def halted_callback_hook(filter, callback_name) # => This method now accepts 2 arguments instead of 1
    Rails.logger.info("Book couldn't be #{callback_name}d")
  end
end

6.4 控制器中的 helper 类方法使用 String#constantize

从概念上讲,在 Rails 6.1 之前

helper "foo/bar"

会导致

require_dependency "foo/bar_helper"
module_name = "foo/bar_helper".camelize
module_name.constantize

现在它改为执行以下操作

prefix = "foo/bar".camelize
"#{prefix}Helper".constantize

此更改对于大多数应用程序来说向后兼容,在这种情况下您无需执行任何操作。

然而,从技术上讲,控制器可以配置 helpers_path 指向 $LOAD_PATH 中不在自动加载路径中的目录。此用例不再开箱即用地受支持。如果辅助模块不可自动加载,应用程序负责在调用 helper 之前加载它。

6.5 从 HTTP 重定向到 HTTPS 将使用 308 HTTP 状态代码

ActionDispatch::SSL 中从 HTTP 重定向非 GET/HEAD 请求到 HTTPS 时使用的默认 HTTP 状态代码已更改为 308,如 https://tools.ietf.org/html/rfc7538 中所定义。

6.6 Active Storage 现在需要 Image Processing

在 Active Storage 中处理变体时,现在需要捆绑 image_processing gem 而不是直接使用 mini_magick。Image Processing 默认配置为在幕后使用 mini_magick,因此最简单的升级方法是将 mini_magick gem 替换为 image_processing gem,并确保删除 combine_options 的显式使用,因为它不再需要。

为了可读性,您可能希望将原始 resize 调用更改为 image_processing 宏。例如,而不是

video.preview(resize: "100x100")
video.preview(resize: "100x100>")
video.preview(resize: "100x100^")

您可以分别执行以下操作

video.preview(resize_to_fit: [100, 100])
video.preview(resize_to_limit: [100, 100])
video.preview(resize_to_fill: [100, 100])

6.7 新的 ActiveModel::Error

错误现在是新的 ActiveModel::Error 类实例,API 已发生更改。其中一些更改可能会根据您操作错误的方式抛出错误,而另一些则会打印弃用警告,以便在 Rails 7.0 中修复。

有关此更改和 API 更改详细信息的更多信息,请参阅 此 PR

7 从 Rails 5.2 升级到 Rails 6.0

有关对 Rails 6.0 进行的更改的更多信息,请参阅 发行说明

7.1 使用 Webpacker

Webpacker 是 Rails 6 的默认 JavaScript 编译器。但如果您要升级应用程序,它默认情况下不会被激活。如果您想使用 Webpacker,则将其包含在您的 Gemfile 中并安装它

gem "webpacker"
$ bin/rails webpacker:install

7.2 强制 SSL

控制器上的 force_ssl 方法已被弃用,将在 Rails 6.1 中删除。建议您启用 config.force_ssl 以在整个应用程序中强制 HTTPS 连接。如果您需要将某些端点免于重定向,可以使用 config.ssl_options 来配置该行为。

7.3 目的和过期元数据现在嵌入在签名和加密的 cookie 中,以提高安全性

为了提高安全性,Rails 将目的和过期元数据嵌入在加密或签名 cookie 值中。

Rails 然后可以阻止尝试复制 cookie 的签名/加密值并将其用作另一个 cookie 的值的攻击。

这种新的嵌入元数据使这些 cookie 与低于 6.0 版本的 Rails 不兼容。

如果您需要您的 cookie 被 Rails 5.2 和更早版本读取,或者您仍然在验证 6.0 部署并希望能够回滚,请将 Rails.application.config.action_dispatch.use_cookies_with_metadata 设置为 false

7.4 所有 npm 包都已移至 @rails 范围

如果您以前通过 npm/yarn 加载了任何 actioncableactivestoragerails-ujs 包,则必须更新这些依赖项的名称才能将它们升级到 6.0.0

actioncable   → @rails/actioncable
activestorage → @rails/activestorage
rails-ujs     → @rails/ujs

7.5 Action Cable JavaScript API 更改

Action Cable JavaScript 包已从 CoffeeScript 转换为 ES2015,我们现在在 npm 分发中发布源代码。

此版本对 Action Cable JavaScript API 的可选部分进行了一些重大更改

  • WebSocket 适配器和日志记录器适配器的配置已从 ActionCable 的属性移至 ActionCable.adapters 的属性。如果您正在配置这些适配器,您需要进行这些更改

    -    ActionCable.WebSocket = MyWebSocket
    +    ActionCable.adapters.WebSocket = MyWebSocket
    
    -    ActionCable.logger = myLogger
    +    ActionCable.adapters.logger = myLogger
    
  • ActionCable.startDebugging()ActionCable.stopDebugging() 方法已被删除,并替换为 ActionCable.logger.enabled 属性。如果您正在使用这些方法,您需要进行这些更改

    -    ActionCable.startDebugging()
    +    ActionCable.logger.enabled = true
    
    -    ActionCable.stopDebugging()
    +    ActionCable.logger.enabled = false
    

7.6 ActionDispatch::Response#content_type 现在返回未修改的 Content-Type 标头

以前,ActionDispatch::Response#content_type 的返回值不包含字符集部分。此行为已更改为包括以前省略的字符集部分。

如果您只需要 MIME 类型,请改为使用 ActionDispatch::Response#media_type

之前

resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16")
resp.content_type #=> "text/csv; header=present"

之后

resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16")
resp.content_type #=> "text/csv; header=present; charset=utf-16"
resp.media_type   #=> "text/csv"

7.7 新的 config.hosts 设置

Rails 现在有一个新的 config.hosts 设置,用于安全目的。此设置在开发环境中的默认值为 localhost。如果您在开发环境中使用其他域,您需要像这样允许它们

# config/environments/development.rb

config.hosts << "dev.myapp.com"
config.hosts << /[a-z0-9-]+\.myapp\.com/ # Optionally, regexp is allowed as well

对于其他环境,config.hosts 默认情况下为空,这意味着 Rails 根本不会验证主机。如果要在生产环境中验证主机,可以选择添加它们。

7.8 自动加载

Rails 6 的默认配置

# config/application.rb

config.load_defaults 6.0

在 CRuby 上启用 zeitwerk 自动加载模式。在这种模式下,自动加载、重新加载和急切加载由 Zeitwerk 管理。

如果您使用的是以前 Rails 版本的默认值,您可以像这样启用 zeitwerk

# config/application.rb

config.autoloader = :zeitwerk

7.8.1 公共 API

通常,应用程序不需要直接使用 Zeitwerk 的 API。Rails 会根据现有契约进行设置:config.autoload_pathsconfig.cache_classes 等。

虽然应用程序应该坚持使用该接口,但实际的 Zeitwerk 加载器对象可以像这样访问

Rails.autoloaders.main

如果您需要预加载单表继承 (STI) 类或配置自定义词形分析器,这可能很方便。

7.8.2 项目结构

如果要升级的应用程序正确自动加载,则项目结构应该已经基本兼容。

但是,classic 模式从缺失的常量名称(underscore)推断文件名,而 zeitwerk 模式从文件名(camelize)推断常量名称。这些帮助程序并不总是彼此的逆,尤其是当涉及到首字母缩略词时。例如,"FOO".underscore"foo",但 "foo".camelize"Foo",而不是 "FOO"

可以使用 zeitwerk:check 任务检查兼容性

$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!

7.8.3 require_dependency

已消除所有已知的 require_dependency 用例,您应该使用 grep 搜索项目并删除它们。

如果您的应用程序使用单表继承,请参阅自动加载和重新加载常量 (Zeitwerk 模式) 指南的单表继承部分

7.8.4 类和模块定义中的限定名称

您现在可以在类和模块定义中可靠地使用常量路径

# Autoloading in this class' body matches Ruby semantics now.
class Admin::UsersController < ApplicationController
  # ...
end

需要注意的是,根据执行顺序,经典自动加载器有时能够在以下情况下自动加载 Foo::Wadus

class Foo::Bar
  Wadus
end

这与 Ruby 语义不匹配,因为 Foo 不在嵌套中,并且在 zeitwerk 模式下根本不起作用。如果您发现这种极端情况,可以使用限定名称 Foo::Wadus

class Foo::Bar
  Foo::Wadus
end

或将 Foo 添加到嵌套中

module Foo
  class Bar
    Wadus
  end
end

7.8.5 关注点

您可以从像这样的标准结构中自动加载和急切加载

app/models
app/models/concerns

在这种情况下,app/models/concerns 被假定为根目录(因为它属于自动加载路径),并且它被忽略为命名空间。因此,app/models/concerns/foo.rb 应该定义 Foo,而不是 Concerns::Foo

Concerns:: 命名空间在经典自动加载器中作为实现的副作用起作用,但这并不是预期的行为。使用 Concerns:: 的应用程序需要重命名这些类和模块才能在 zeitwerk 模式下运行。

7.8.6 在自动加载路径中包含 app

一些项目希望在 app/api/base.rb 中定义 API::Base,并在 classic 模式下将 app 添加到自动加载路径中以实现此目的。由于 Rails 会自动将 app 的所有子目录添加到自动加载路径中,因此我们遇到了另一种嵌套根目录的情况,因此这种设置不再有效。与上面的 concerns 说明的原理类似。

如果您想保留这种结构,则需要在初始化器中从自动加载路径中删除子目录。

ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/api")

7.8.7 自动加载的常量和显式命名空间

如果命名空间在文件中定义,例如 Hotel 在这里

app/models/hotel.rb         # Defines Hotel.
app/models/hotel/pricing.rb # Defines Hotel::Pricing.

则必须使用 classmodule 关键字设置 Hotel 常量。例如

class Hotel
end

很好。

像这样的替代方案

Hotel = Class.new

Hotel = Struct.new

将不起作用,子对象如 Hotel::Pricing 将找不到。

此限制仅适用于显式命名空间。没有定义命名空间的类和模块可以使用这些习惯用法定义。

7.8.8 一个文件,一个常量(在同一顶级)

classic 模式下,您可以在技术上在同一顶级定义多个常量,并让它们全部重新加载。例如,给定

# app/models/foo.rb

class Foo
end

class Bar
end

虽然 Bar 无法自动加载,但自动加载 Foo 会将 Bar 标记为已自动加载。在 zeitwerk 模式下并非如此,您需要将 Bar 移动到它自己的文件 bar.rb 中。一个文件,一个常量。

这仅适用于与上面示例中相同的顶级的常量。内部类和模块是可以的。例如,考虑

# app/models/foo.rb

class Foo
  class InnerClass
  end
end

如果应用程序重新加载 Foo,它也会重新加载 Foo::InnerClass

7.8.9 Spring 和 test 环境

如果出现更改,Spring 会重新加载应用程序代码。在 test 环境中,您需要启用重新加载才能使其正常工作。

# config/environments/test.rb

config.cache_classes = false

否则,您将收到此错误

reloading is disabled because config.cache_classes is true

7.8.10 Bootsnap

Bootsnap 应至少为 1.4.2 版本。

除此之外,如果运行 Ruby 2.5,Bootsnap 需要由于解释器中的错误而禁用 iseq 缓存。在这种情况下,请确保至少依赖于 Bootsnap 1.4.4。

7.8.11 config.add_autoload_paths_to_load_path

新的配置点 config.add_autoload_paths_to_load_path 默认情况下为 true,以实现向后兼容性,但允许您选择不将自动加载路径添加到 $LOAD_PATH

这在大多数应用程序中是有意义的,因为您永远不应该在 app/models 中要求文件,例如,Zeitwerk 在内部仅使用绝对文件名。

通过选择退出,您可以优化 $LOAD_PATH 查找(减少要检查的目录),并节省 Bootsnap 的工作量和内存消耗,因为它不需要为这些目录建立索引。

7.8.12 线程安全

在经典模式下,常量自动加载不是线程安全的,尽管 Rails 在例如 Web 请求中启用了自动加载的情况下,仍然存在锁来确保线程安全,这在开发环境中很常见。

zeitwerk 模式下,常量自动加载是线程安全的。例如,您现在可以在由 runner 命令执行的多线程脚本中自动加载。

7.8.13 config.autoload_paths 中的通配符

注意像这样的配置

config.autoload_paths += Dir["#{config.root}/lib/**/"]

config.autoload_paths 的每个元素都应该代表顶级命名空间(Object),因此它们不能嵌套(除了上面解释的 concerns 目录)。

要修复此问题,只需删除通配符即可。

config.autoload_paths << "#{config.root}/lib"

7.8.14 提前加载和自动加载是一致的

classic 模式下,如果 app/models/foo.rb 定义了 Bar,您将无法自动加载该文件,但提前加载会起作用,因为它会盲目地递归加载文件。如果您先测试提前加载,这可能是错误的来源,执行可能在以后自动加载时失败。

zeitwerk 模式下,两种加载模式都是一致的,它们都会在相同的文件中失败并报错。

7.8.15 如何在 Rails 6 中使用经典自动加载器

应用程序可以加载 Rails 6 默认值,并通过以下方式设置 config.autoloader 来继续使用经典自动加载器

# config/application.rb

config.load_defaults 6.0
config.autoloader = :classic

在 Rails 6 应用程序中使用经典自动加载器时,建议在开发环境中将 Web 服务器和后台处理器的并发级别设置为 1,以解决线程安全问题。

7.9 Active Storage 赋值行为更改

使用 Rails 5.2 的配置默认值,为使用 has_many_attached 声明的附件集合赋值会附加新文件

class User < ApplicationRecord
  has_many_attached :highlights
end

user.highlights.attach(filename: "funky.jpg")
user.highlights.count # => 1

blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg")
user.update!(highlights: [ blob ])

user.highlights.count # => 2
user.highlights.first.filename # => "funky.jpg"
user.highlights.second.filename # => "town.jpg"

使用 Rails 6.0 的配置默认值,为附件集合赋值会替换现有文件,而不是附加它们。这与为集合关联赋值时 Active Record 的行为相匹配

user.highlights.attach(filename: "funky.jpg")
user.highlights.count # => 1

blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg")
user.update!(highlights: [ blob ])

user.highlights.count # => 1
user.highlights.first.filename # => "town.jpg"

#attach 可用于添加新附件,而不会删除现有附件

blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg")
user.highlights.attach(blob)

user.highlights.count # => 2
user.highlights.first.filename # => "funky.jpg"
user.highlights.second.filename # => "town.jpg"

现有应用程序可以通过将 config.active_storage.replace_on_assign_to_many 设置为 true 来选择加入此新行为。旧行为将在 Rails 7.0 中被弃用,并在 Rails 7.1 中删除。

7.10 自定义异常处理应用程序

无效的 AcceptContent-Type 请求头现在会引发异常。默认的 config.exceptions_app 会专门处理该错误并进行补偿。自定义异常应用程序也需要处理该错误,否则此类请求会导致 Rails 使用回退异常应用程序,该应用程序返回 500 内部服务器错误

8 从 Rails 5.1 升级到 Rails 5.2

有关对 Rails 5.2 进行的更改的更多信息,请参阅 发行说明

8.1 Bootsnap

Rails 5.2 在 新生成的应用程序的 Gemfile 中添加了 bootsnap gem。app:update 命令将其在 boot.rb 中设置。如果您想使用它,请在 Gemfile 中添加它

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false

否则,将 boot.rb 更改为不使用 bootsnap。

为了提高安全性,Rails 现在还在加密或签名 cookie 值中嵌入过期时间信息。

这种新的嵌入信息使这些 cookie 与低于 5.2 版本的 Rails 版本不兼容。

如果您需要旧的 5.1 版本或更低版本读取您的 cookie,或者您仍在验证 5.2 部署,并且希望允许您回滚,请将 Rails.application.config.action_dispatch.use_authenticated_cookie_encryption 设置为 false

9 从 Rails 5.0 升级到 Rails 5.1

有关对 Rails 5.1 进行的更改的更多信息,请参阅 发行说明

9.1 顶级 HashWithIndifferentAccess 已被软弃用

如果您的应用程序使用顶级 HashWithIndifferentAccess 类,您应该慢慢地将您的代码迁移到改用 ActiveSupport::HashWithIndifferentAccess

它只是被软弃用,这意味着您的代码目前不会中断,也不会显示弃用警告,但该常量将在将来被删除。

此外,如果您拥有包含此类对象转储的非常旧的 YAML 文档,您可能需要再次加载和转储它们,以确保它们引用了正确的常量,并且将来加载它们不会中断。

9.2 application.secrets 现在以所有键为符号加载

如果您的应用程序将嵌套配置存储在 config/secrets.yml 中,现在所有键都以符号加载,因此使用字符串访问应该更改。

Rails.application.secrets[:smtp_settings]["address"]

Rails.application.secrets[:smtp_settings][:address]

9.3 删除了对 render 中的 :text:nothing 的弃用支持

如果您的控制器使用 render :text,它们将不再起作用。使用 text/plain MIME 类型的文本渲染新方法是使用 render :plain

类似地,render :nothing 也被删除,您应该使用 head 方法来发送仅包含头的响应。例如,head :ok 发送一个 200 响应,没有要渲染的正文。

9.4 删除了对 redirect_to :back 的弃用支持

在 Rails 5.0 中,redirect_to :back 已被弃用。在 Rails 5.1 中,它已被完全删除。

作为替代方案,使用 redirect_back。重要的是要注意,redirect_back 也接受一个 fallback_location 选项,该选项将在缺少 HTTP_REFERER 的情况下使用。

redirect_back(fallback_location: root_path)

10 从 Rails 4.2 升级到 Rails 5.0

有关对 Rails 5.0 进行的更改的更多信息,请参阅 发行说明

10.1 要求 Ruby 2.2.2+

从 Ruby on Rails 5.0 开始,Ruby 2.2.2+ 是唯一支持的 Ruby 版本。在继续之前,请确保您使用的是 Ruby 2.2.2 版本或更高版本。

10.2 Active Record 模型现在默认继承自 ApplicationRecord

在 Rails 4.2 中,Active Record 模型继承自 ActiveRecord::Base。在 Rails 5.0 中,所有模型都继承自 ApplicationRecord

ApplicationRecord 是所有应用程序模型的新超类,类似于应用程序控制器继承自 ApplicationController 而不是 ActionController::Base。这使应用程序能够在一个位置配置应用程序范围的模型行为。

从 Rails 4.2 升级到 Rails 5.0 时,您需要在 app/models/ 中创建一个 application_record.rb 文件,并添加以下内容

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

然后确保所有模型都继承自它。

10.3 通过 throw(:abort) 停止回调链

在 Rails 4.2 中,当 Active Record 和 Active Model 中的“before”回调返回 false 时,整个回调链将被停止。换句话说,不会执行后续的“before”回调,也不会执行包装在回调中的操作。

在 Rails 5.0 中,在 Active Record 或 Active Model 回调中返回 false 不会有这种停止回调链的副作用。相反,必须通过调用 throw(:abort) 来显式停止回调链。

从 Rails 4.2 升级到 Rails 5.0 时,在这些回调中返回 false 仍然会停止回调链,但您将收到有关此即将到来的更改的弃用警告。

准备好后,您可以选择加入新的行为并删除弃用警告,方法是在 config/application.rb 中添加以下配置

ActiveSupport.halt_callback_chains_on_return_false = false

请注意,此选项不会影响 Active Support 回调,因为当返回任何值时,它们从未停止过链。

有关更多详细信息,请参阅 #17227

10.4 ActiveJob 现在默认继承自 ApplicationJob

在 Rails 4.2 中,Active Job 继承自 ActiveJob::Base。在 Rails 5.0 中,此行为已更改为现在继承自 ApplicationJob

从 Rails 4.2 升级到 Rails 5.0 时,您需要在 app/jobs/ 中创建一个 application_job.rb 文件,并添加以下内容

class ApplicationJob < ActiveJob::Base
end

然后确保所有作业类都继承自它。

有关更多详细信息,请参阅 #19034

10.5 Rails 控制器测试

10.5.1 将一些辅助方法提取到 rails-controller-testing

assignsassert_template 已被提取到 rails-controller-testing gem 中。要在您的控制器测试中继续使用这些方法,请将 gem "rails-controller-testing" 添加到您的 Gemfile 中。

如果您使用 RSpec 进行测试,请参阅 gem 文档中所需的额外配置。

10.5.2 上传文件时的新的行为

如果您在测试中使用ActionDispatch::Http::UploadedFile上传文件,您需要更改为使用类似的Rack::Test::UploadedFile类。

有关更多详细信息,请参见#26404

10.6 生产环境中启动后自动加载已禁用

默认情况下,生产环境中启动后,自动加载现已禁用。

急切加载应用程序是启动过程的一部分,因此顶级常量是可以的,并且仍然可以自动加载,无需在其文件中进行要求。

在运行时仅执行的更深位置的常量(如常规方法体)也是可以的,因为定义它们的将已在启动时急切加载。

对于绝大多数应用程序,此更改无需任何操作。但在极少数情况下,您的应用程序需要在生产环境中运行时自动加载,请将Rails.application.config.enable_dependency_loading设置为true。

10.7 XML序列化

ActiveModel::Serializers::Xml已从Rails提取到activemodel-serializers-xml gem中。要继续在您的应用程序中使用XML序列化,请在您的Gemfile中添加gem "activemodel-serializers-xml"

10.8 删除对旧版mysql数据库适配器的支持

Rails 5 删除了对旧版mysql数据库适配器的支持。大多数用户应该能够使用mysql2代替。当我们找到有人维护它时,它将被转换为一个单独的 gem。

10.9 删除对调试器的支持

Rails 5 要求使用 Ruby 2.2,因此不再支持debugger。请改用byebug

10.10 使用bin/rails运行任务和测试

Rails 5 添加了通过bin/rails而不是rake运行任务和测试的功能。通常情况下,这些更改与rake并行进行,但有些更改已经完全移植过来。

要使用新的测试运行器,只需键入bin/rails test

rake dev:cache现在是bin/rails dev:cache

在您的应用程序的根目录中运行bin/rails,以查看可用的命令列表。

10.11 ActionController::Parameters不再继承自HashWithIndifferentAccess

在您的应用程序中调用params现在将返回一个对象,而不是一个哈希。如果您的参数已经获得许可,那么您将不需要进行任何更改。如果您正在使用map和其他依赖于能够读取哈希的方法,无论permitted?如何,您都需要将您的应用程序升级到首先允许,然后转换为哈希。

params.permit([:proceed_to, :return_to]).to_h

10.12 protect_from_forgery现在默认情况下为prepend: false

protect_from_forgery默认情况下为prepend: false,这意味着它将被插入到回调链中,在您在应用程序中调用它的位置。如果您希望protect_from_forgery始终首先运行,那么您应该将应用程序更改为使用protect_from_forgery prepend: true

10.13 默认模板处理程序现在为RAW

扩展名中没有模板处理程序的文件将使用原始处理程序呈现。以前,Rails 将使用 ERB 模板处理程序呈现文件。

如果您不希望您的文件通过原始处理程序进行处理,那么您应该向文件添加一个扩展名,该扩展名可以由相应的模板处理程序解析。

10.14 为模板依赖项添加了通配符匹配

您现在可以对模板依赖项使用通配符匹配。例如,如果您将模板定义为这样

<% # Template Dependency: recordings/threads/events/subscribers_changed %>
<% # Template Dependency: recordings/threads/events/completed %>
<% # Template Dependency: recordings/threads/events/uncompleted %>

您现在只需使用通配符调用一次依赖项。

<% # Template Dependency: recordings/threads/events/* %>

10.15 ActionView::Helpers::RecordTagHelper移动到外部 gem(record_tag_helper)

content_tag_fordiv_for已被删除,取而代之的是仅使用content_tag。要继续使用旧方法,请将record_tag_helper gem 添加到您的Gemfile

gem "record_tag_helper", "~> 1.0"

有关更多详细信息,请参见#18411

10.16 删除了对protected_attributes gem的支持

Rails 5 中不再支持protected_attributes gem。

10.17 删除了对activerecord-deprecated_finders gem的支持

Rails 5 中不再支持activerecord-deprecated_finders gem。

10.18 ActiveSupport::TestCase默认测试顺序现在为随机

在您的应用程序中运行测试时,默认顺序现在是:random而不是:sorted。使用以下配置选项将其设置回:sorted

# config/environments/test.rb
Rails.application.configure do
  config.active_support.test_order = :sorted
end

10.19 ActionController::Live已成为一个Concern

如果您在另一个包含在您的控制器中的模块中包含ActionController::Live,那么您也应该使用ActiveSupport::Concern扩展该模块。或者,您可以使用self.included钩子,在包含StreamingSupport后,直接将ActionController::Live包含到控制器中。

这意味着,如果您的应用程序以前有自己的流模块,那么以下代码将在生产环境中中断

# This is a work-around for streamed controllers performing authentication with Warden/Devise.
# See https://github.com/plataformatec/devise/issues/2332
# Authenticating in the router is another solution as suggested in that issue
class StreamingSupport
  include ActionController::Live # this won't work in production for Rails 5
  # extend ActiveSupport::Concern # unless you uncomment this line.

  def process(name)
    super(name)
  rescue ArgumentError => e
    if e.message == "uncaught throw :warden"
      throw :warden
    else
      raise e
    end
  end
end

10.20 新的框架默认值

10.20.1 Active Record belongs_to默认情况下需要选项

如果关联不存在,belongs_to现在将默认情况下触发验证错误。

这可以通过optional: true在每个关联中关闭。

此默认值将在新的应用程序中自动配置。如果现有应用程序想要添加此功能,则需要在初始化器中启用它

config.active_record.belongs_to_required_by_default = true

默认情况下,此配置对您所有模型都是全局的,但您可以在每个模型的基础上覆盖它。这将帮助您将所有模型迁移到默认情况下要求其关联。

class Book < ApplicationRecord
  # model is not yet ready to have its association required by default

  self.belongs_to_required_by_default = false
  belongs_to(:author)
end

class Car < ApplicationRecord
  # model is ready to have its association required by default

  self.belongs_to_required_by_default = true
  belongs_to(:pilot)
end

10.20.2 每个表单的 CSRF 令牌

Rails 5 现在支持每个表单的 CSRF 令牌,以减轻 JavaScript 创建的表单的代码注入攻击。启用此选项后,您的应用程序中的表单将分别具有自己的 CSRF 令牌,该令牌特定于该表单的操作和方法。

config.action_controller.per_form_csrf_tokens = true

10.20.3 带有来源检查的伪造保护

您现在可以将应用程序配置为检查 HTTP Origin 标头是否应针对网站的来源进行检查,作为额外的 CSRF 防御。在您的配置中将以下设置设置为 true

config.action_controller.forgery_protection_origin_check = true

10.20.4 允许配置 Action Mailer 队列名称

默认邮件队列名称为mailers。此配置选项允许您全局更改队列名称。在您的配置中设置以下内容

config.action_mailer.deliver_later_queue_name = :new_queue_name

10.20.5 支持 Action Mailer 视图中的片段缓存

在您的配置中设置config.action_mailer.perform_caching,以确定您的 Action Mailer 视图是否应支持缓存。

config.action_mailer.perform_caching = true

10.20.6 配置db:structure:dump的输出

如果您使用的是schema_search_path或其他 PostgreSQL 扩展,则可以控制如何转储模式。设置为:all以生成所有转储,或设置为:schema_search_path以从模式搜索路径生成。

config.active_record.dump_schemas = :all

10.20.7 配置 SSL 选项以启用带有子域的 HSTS

在您的配置中设置以下内容,以在使用子域时启用 HSTS

config.ssl_options = { hsts: { subdomains: true } }

10.20.8 保留接收者的时区

在使用 Ruby 2.4 时,您可以在调用to_time时保留接收者的时区。

ActiveSupport.to_time_preserves_timezone = false

10.21 JSON/JSONB 序列化更改

在 Rails 5.0 中,JSON/JSONB 属性的序列化和反序列化方式发生了变化。现在,如果您将列设置为String,Active Record 将不再将该字符串转换为Hash,而是仅返回该字符串。这不仅限于与模型交互的代码,还会影响db/schema.rb中的:default列设置。建议您不要将列设置为String,而是传递一个Hash,它将自动转换为 JSON 字符串并从 JSON 字符串转换回来。

11 从 Rails 4.1 升级到 Rails 4.2

11.1 Web 控制台

首先,在您的Gemfile中将gem "web-console", "~> 2.0"添加到:development组,然后运行bundle install(在您升级 Rails 时,它不会包含在内)。安装完成后,您只需将对控制台助手的引用(即<%= console %>)放到您要为其启用任何视图中。在开发环境中查看的任何错误页面上,也将提供一个控制台。

11.2 响应者

respond_with和类级别的respond_to方法已提取到responders gem 中。要使用它们,只需在您的Gemfile中添加gem "responders", "~> 2.0"。在没有在您的依赖项中包含responders gem 的情况下,对respond_withrespond_to(再次,在类级别)的调用将不再起作用

# app/controllers/users_controller.rb

class UsersController < ApplicationController
  respond_to :html, :json

  def show
    @user = User.find(params[:id])
    respond_with @user
  end
end

实例级别的respond_to不受影响,不需要额外的 gem

# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    respond_to do |format|
      format.html
      format.json { render json: @user }
    end
  end
end

有关更多详细信息,请参见#16526

11.3 事务回调中的错误处理

目前,Active Record 会抑制在after_rollbackafter_commit回调中引发的错误,并且只会将它们打印到日志中。在下一个版本中,这些错误将不再被抑制。相反,错误将像其他 Active Record 回调一样正常传播。

当您定义after_rollbackafter_commit回调时,您将收到有关此即将发生的更改的弃用警告。当您准备就绪时,您可以选择新的行为并通过将以下配置添加到您的config/application.rb中来删除弃用警告

config.active_record.raise_in_transactional_callbacks = true

有关更多详细信息,请参见#14488#16537

11.4 测试用例的排序

在 Rails 5.0 中,测试用例默认情况下将以随机顺序执行。为了应对这一变化,Rails 4.2 引入了一个新的配置选项active_support.test_order,用于显式指定测试排序。这允许您通过将选项设置为:sorted来锁定当前行为,或通过将选项设置为:random来选择未来的行为。

如果您没有为该选项指定一个值,则将发出一个弃用警告。要避免这种情况,请将以下行添加到您的测试环境中

# config/environments/test.rb
Rails.application.configure do
  config.active_support.test_order = :sorted # or `:random` if you prefer
end

11.5 序列化属性

在使用自定义编码器(例如serialize :metadata, JSON)时,将nil分配给序列化属性将将其保存到数据库中,作为NULL而不是通过编码器传递nil值(例如,使用JSON编码器时为"null")。

11.6 生产日志级别

在 Rails 5 中,生产环境的默认日志级别将从:info更改为:debug。要保留当前默认值,请将以下行添加到您的production.rb

# Set to `:info` to match the current default, or set to `:debug` to opt-into
# the future default.
config.log_level = :info

11.7 Rails 模板中的after_bundle

如果您有一个 Rails 模板,它将版本控制中的所有文件都添加进来,那么它无法添加生成的 binstubs,因为它在 Bundler 之前执行

# template.rb
generate(:scaffold, "person name:string")
route "root to: 'people#index'"
rake("db:migrate")

git :init
git add: "."
git commit: %Q{ -m 'Initial commit' }

您现在可以将git调用包装在after_bundle块中。它将在生成 binstubs 后运行。

# template.rb
generate(:scaffold, "person name:string")
route "root to: 'people#index'"
rake("db:migrate")

after_bundle do
  git :init
  git add: "."
  git commit: %Q{ -m 'Initial commit' }
end

11.8 Rails HTML 清理器

在您的应用程序中,您可以选择新的方法来清理 HTML 片段。现在,著名的 html-scanner 方法正式被弃用,取而代之的是Rails HTML 清理器

这意味着方法sanitizesanitize_cssstrip_tagsstrip_links由一个新的实现支持。

这个新的清理器在内部使用Loofah。Loofah 又使用 Nokogiri,它封装了用 C 和 Java 编写的 XML 解析器,因此清理速度应该更快,无论您运行哪个 Ruby 版本。

新版本更新了sanitize,因此它可以接受Loofah::Scrubber,以进行强大的清理。 在此处查看一些清理器的示例

还添加了两个新的清理器:PermitScrubberTargetScrubber。 阅读gem 的自述文件,了解更多信息。

PermitScrubberTargetScrubber的文档解释了如何完全控制何时以及如何剥离元素。

如果您的应用程序需要使用旧的清理器实现,请在您的Gemfile中包含rails-deprecated_sanitizer

gem "rails-deprecated_sanitizer"

11.9 Rails DOM 测试

TagAssertions 模块(包含 assert_tag 等方法)已弃用,取而代之的是 SelectorAssertions 模块中的 assert_select 方法,该模块已提取到 rails-dom-testing gem 中。

11.10 掩盖身份验证令牌

为了减轻 SSL 攻击,form_authenticity_token 现在被掩盖,因此它在每次请求中都会有所不同。 因此,令牌通过取消掩盖然后解密进行验证。 结果,任何依赖于静态会话 CSRF 令牌的策略都必须考虑这一点,以验证来自非 rails 表单的请求。

11.11 Action Mailer

以前,在邮件器类上调用邮件器方法将导致直接执行相应的实例方法。 随着 Active Job 和 #deliver_later 的引入,情况不再如此。 在 Rails 4.2 中,实例方法的调用被推迟,直到调用 deliver_nowdeliver_later。 例如

class Notifier < ActionMailer::Base
  def notify(user)
    puts "Called"
    mail(to: user.email)
  end
end
mail = Notifier.notify(user) # Notifier#notify is not yet called at this point
mail = mail.deliver_now           # Prints "Called"

这对于大多数应用程序来说应该不会造成任何明显的区别。 但是,如果您需要一些非邮件器方法同步执行,并且您之前依赖于同步代理行为,则应将它们直接定义为邮件器类的类方法

class Notifier < ActionMailer::Base
  def self.broadcast_notifications(users, ...)
    users.each { |user| Notifier.notify(user, ...) }
  end
end

11.12 外键支持

迁移 DSL 已扩展为支持外键定义。 如果您一直使用 Foreigner gem,您可能希望考虑将其删除。 请注意,Rails 的外键支持是 Foreigner 的一个子集。 这意味着并非所有 Foreigner 定义都可以完全用其 Rails 迁移 DSL 等效项替换。

迁移过程如下

  1. Gemfile 中删除 gem "foreigner"
  2. 运行 bundle install
  3. 运行 bin/rake db:schema:dump
  4. 确保 db/schema.rb 包含所有具有必要选项的外键定义。

12 从 Rails 4.0 升级到 Rails 4.1

12.1 来自远程 <script> 标签的 CSRF 保护

或者,“我的测试为什么失败了!?!” 或者 “我的 <script> 小部件坏了!!“

跨站点请求伪造 (CSRF) 保护现在也涵盖了带有 JavaScript 响应的 GET 请求。 这可以防止第三方站点通过 <script> 标签远程引用您的 JavaScript 以提取敏感数据。

这意味着您的使用以下内容的功能和集成测试

get :index, format: :js

现在将触发 CSRF 保护。 切换到

xhr :get, :index, format: :js

以明确测试 XmlHttpRequest

您自己的 <script> 标签默认情况下也被视为跨域并被阻止。 如果您确实要从 <script> 标签加载 JavaScript,则现在必须明确跳过这些操作的 CSRF 保护。

12.2 Spring

如果您想使用 Spring 作为您的应用程序预加载器,您需要

  1. gem "spring", group: :development 添加到您的 Gemfile 中。
  2. 使用 bundle install 安装 spring。
  3. 使用 bundle exec spring binstub 生成 Spring binstub。

默认情况下,用户定义的 rake 任务将在 development 环境中运行。 如果您想让他们在其他环境中运行,请参考 Spring README

12.3 config/secrets.yml

如果您想使用新的 secrets.yml 约定来存储应用程序的机密,您需要

  1. 在您的 config 文件夹中创建一个 secrets.yml 文件,内容如下

    development:
      secret_key_base:
    
    test:
      secret_key_base:
    
    production:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
    
  2. 使用您从 secret_token.rb 初始化器中获取的现有 secret_key_base 为任何在生产环境中运行 Rails 应用程序的用户设置 SECRET_KEY_BASE 环境变量。 或者,您只需将 secret_token.rb 初始化器中的现有 secret_key_base 复制到 secrets.ymlproduction 部分,替换 <%= ENV["SECRET_KEY_BASE"] %>

  3. 删除 secret_token.rb 初始化器。

  4. 使用 rake secretdevelopmenttest 部分生成新的密钥。

  5. 重新启动您的服务器。

12.4 测试帮助程序的更改

如果您的测试帮助程序包含对 ActiveRecord::Migration.check_pending! 的调用,则可以将其删除。 现在当您 require "rails/test_help" 时会自动执行此检查,尽管将此行保留在您的帮助程序中不会造成任何伤害。

12.5 Cookies 序列化器

在 Rails 4.1 之前创建的应用程序使用 Marshal 将 Cookie 值序列化到签名和加密的 Cookie 罐中。 如果您想在您的应用程序中使用新的基于 JSON 的格式,您可以在添加一个具有以下内容的初始化器文件

Rails.application.config.action_dispatch.cookies_serializer = :hybrid

这将透明地将您现有的 Marshal 序列化 Cookie 迁移到新的基于 JSON 的格式。

使用 :json:hybrid 序列化器时,请注意,并非所有 Ruby 对象都可以序列化为 JSON。 例如,DateTime 对象将被序列化为字符串,而 Hash 的键将被字符串化。

class CookiesController < ApplicationController
  def set_cookie
    cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
    redirect_to action: "read_cookie"
  end

  def read_cookie
    cookies.encrypted[:expiration_date] # => "2014-03-20"
  end
end

建议您只在 Cookie 中存储简单数据(字符串和数字)。 如果您必须存储复杂对象,则需要在后续请求中读取值时手动处理转换。

如果您使用 Cookie 会话存储,则这将适用于 sessionflash 哈希。

12.6 闪存结构更改

闪存消息键被规范化为字符串。 它们仍然可以通过符号或字符串访问。 遍历闪存将始终生成字符串键

flash["string"] = "a string"
flash[:symbol] = "a symbol"

# Rails < 4.1
flash.keys # => ["string", :symbol]

# Rails >= 4.1
flash.keys # => ["string", "symbol"]

确保您正在将闪存消息键与字符串进行比较。

12.7 JSON 处理中的更改

Rails 4.1 中的 JSON 处理存在一些重大更改。

12.7.1 MultiJSON 移除

MultiJSON 已达到 使用寿命终点,已从 Rails 中移除。

如果您的应用程序目前直接依赖于 MultiJSON,您有几个选择

  1. 将 'multi_json' 添加到您的 Gemfile 中。 请注意,这在将来可能不再有效

  2. 通过使用 obj.to_jsonJSON.parse(str) 从 MultiJSON 迁移。

不要简单地将 MultiJson.dumpMultiJson.load 替换为 JSON.dumpJSON.load。 这些 JSON gem API 用于序列化和反序列化任意 Ruby 对象,通常不安全

12.7.2 JSON gem 兼容性

历史上,Rails 与 JSON gem 存在一些兼容性问题。 在 Rails 应用程序中使用 JSON.generateJSON.dump 可能产生意外错误。

Rails 4.1 通过将自己的编码器与 JSON gem 隔离来修复了这些问题。 JSON gem API 将按正常方式工作,但它们将无法访问任何 Rails 特定功能。 例如

class FooBar
  def as_json(options = nil)
    { foo: "bar" }
  end
end
irb> FooBar.new.to_json
=> "{\"foo\":\"bar\"}"
irb> JSON.generate(FooBar.new, quirks_mode: true)
=> "\"#<FooBar:0x007fa80a481610>\""

12.7.3 新的 JSON 编码器

Rails 4.1 中的 JSON 编码器已重写,以利用 JSON gem。 对于大多数应用程序来说,这应该是一个透明的更改。 但是,作为重写的一部分,以下功能已从编码器中移除

  1. 循环数据结构检测
  2. encode_json hook 的支持
  3. BigDecimal 对象编码为数字而不是字符串的选项

如果您的应用程序依赖于其中一项功能,您可以通过将 activesupport-json_encoder gem 添加到您的 Gemfile 中来找回它们。

12.7.4 Time 对象的 JSON 表示

带有时间组件的对象(TimeDateTimeActiveSupport::TimeWithZone)的 #as_json 现在默认情况下返回毫秒精度。 如果您需要保留旧的行为,没有毫秒精度,请在初始化器中设置以下内容

ActiveSupport::JSON::Encoding.time_precision = 0

12.8 在内联回调块中使用 return

以前,Rails 允许内联回调块以这种方式使用 return

class ReadOnlyModel < ActiveRecord::Base
  before_save { return false } # BAD
end

此行为从未被有意支持。 由于 ActiveSupport::Callbacks 内部结构的更改,在 Rails 4.1 中不再允许这样做。 在内联回调块中使用 return 语句会导致在执行回调时引发 LocalJumpError

可以使用 return 的内联回调块可以被重构为评估为返回值

class ReadOnlyModel < ActiveRecord::Base
  before_save { false } # GOOD
end

或者,如果更喜欢 return,建议明确定义一个方法

class ReadOnlyModel < ActiveRecord::Base
  before_save :before_save_callback # GOOD

  private
    def before_save_callback
      false
    end
end

此更改适用于 Rails 中使用回调的大多数地方,包括 Active Record 和 Active Model 回调,以及 Action Controller 中的过滤器(例如 before_action)。

有关更多详细信息,请参阅 此拉取请求

12.9 在 Active Record 固定文件中定义的方法

Rails 4.1 在单独的上下文中评估每个固定文件的 ERB,因此在固定文件中定义的帮助程序方法将不可用于其他固定文件。

在多个固定文件中使用的帮助程序方法应在模块中定义,这些模块包含在 test_helper.rb 中新引入的 ActiveRecord::FixtureSet.context_class 中。

module FixtureFileHelpers
  def file_sha(path)
    OpenSSL::Digest::SHA256.hexdigest(File.read(Rails.root.join("test/fixtures", path)))
  end
end

ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers

12.10 I18n 强制执行可用语言环境

Rails 4.1 现在将 I18n 选项 enforce_available_locales 的默认值设置为 true。 这意味着它将确保传递给它的所有语言环境都必须在 available_locales 列表中声明。

要禁用它(并允许 I18n 接受任何语言环境选项),请将以下配置添加到您的应用程序中

config.i18n.enforce_available_locales = false

请注意,此选项是作为安全措施添加的,以确保用户输入不能用作语言环境信息,除非它事先已知。 因此,建议您不要禁用此选项,除非您有充分的理由这样做。

12.11 在 Relation 上调用的变异器方法

Relation 不再具有变异器方法,例如 #map!#delete_if。 通过调用 #to_a 将其转换为 Array,然后再使用这些方法。

它旨在防止在直接在 Relation 上调用变异器方法的代码中出现奇怪的错误和混淆。

# Instead of this
Author.where(name: "Hank Moody").compact!

# Now you have to do this
authors = Author.where(name: "Hank Moody").to_a
authors.compact!

12.12 默认作用域的更改

默认作用域不再被链式条件覆盖。

在之前的版本中,当你定义了一个模型中的 default_scope 时,它会被同一字段中链式条件覆盖。现在它就像任何其他作用域一样合并。

之前

class User < ActiveRecord::Base
  default_scope { where state: "pending" }
  scope :active, -> { where state: "active" }
  scope :inactive, -> { where state: "inactive" }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'

User.where(state: "inactive")
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

之后

class User < ActiveRecord::Base
  default_scope { where state: "pending" }
  scope :active, -> { where state: "active" }
  scope :inactive, -> { where state: "inactive" }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'

User.where(state: "inactive")
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'

为了获得之前的行为,需要使用 unscopedunscoperewhereexcept 显式地删除 default_scope 条件。

class User < ActiveRecord::Base
  default_scope { where state: "pending" }
  scope :active, -> { unscope(where: :state).where(state: "active") }
  scope :inactive, -> { rewhere state: "inactive" }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'

User.inactive
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

12.13 从字符串渲染内容

Rails 4.1 引入了 :plain:html:body 选项到 render。这些选项现在是渲染基于字符串的内容的首选方式,因为它允许你指定想要将响应发送的哪种内容类型。

  • render :plain 将把内容类型设置为 text/plain
  • render :html 将把内容类型设置为 text/html
  • render :body 设置内容类型头。

从安全角度来看,如果你不希望在响应主体中包含任何标记,你应该使用 render :plain,因为大多数浏览器会为你转义响应中的不安全内容。

我们将在未来版本中弃用 render :text 的使用。因此,请开始使用更精确的 :plain:html:body 选项。使用 render :text 可能存在安全风险,因为内容将作为 text/html 发送。

12.14 PostgreSQL JSON 和 hstore 数据类型

Rails 4.1 将把 jsonhstore 列映射到一个字符串键的 Ruby Hash。在早期版本中,使用的是 HashWithIndifferentAccess。这意味着不再支持符号访问。对于基于 jsonhstore 列的 store_accessors 也是如此。确保始终使用字符串键。

12.15 ActiveSupport::Callbacks 中的显式块使用

Rails 4.1 现在要求在调用 ActiveSupport::Callbacks.set_callback 时传递一个显式块。这一变化源于 ActiveSupport::Callbacks 在 4.1 版本中被大量重写。

# Previously in Rails 4.0
set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }

# Now in Rails 4.1
set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }

13 从 Rails 3.2 升级到 Rails 4.0

如果你的应用程序当前运行在任何低于 3.2.x 版本的 Rails 版本上,你应该先升级到 Rails 3.2,然后再尝试升级到 Rails 4.0。

以下更改用于将你的应用程序升级到 Rails 4.0。

13.1 HTTP PATCH

Rails 4 现在使用 PATCH 作为更新的主要 HTTP 动词,当在 config/routes.rb 中声明 RESTful 资源时。update 操作仍然在使用,PUT 请求也将继续被路由到 update 操作。因此,如果你只使用标准的 RESTful 路由,则无需进行任何更改

resources :users
<%= form_for @user do |f| %>
class UsersController < ApplicationController
  def update
    # No change needed; PATCH will be preferred, and PUT will still work.
  end
end

但是,如果你使用 form_for 更新资源,并结合使用自定义路由使用 PUT HTTP 方法,则需要进行更改

resources :users do
  put :update_name, on: :member
end
<%= form_for [ :update_name, @user ] do |f| %>
class UsersController < ApplicationController
  def update_name
    # Change needed; form_for will try to use a non-existent PATCH route.
  end
end

如果操作没有在公共 API 中使用,并且你可以自由更改 HTTP 方法,你可以更新你的路由,使用 patch 代替 put

resources :users do
  patch :update_name, on: :member
end

在 Rails 4 中,PUT 请求到 /users/:id 会被路由到 update,就像今天一样。因此,如果你有一个接收真实 PUT 请求的 API,它将继续工作。路由器还会将 PATCH 请求到 /users/:id 路由到 update 操作。

如果操作在公共 API 中使用,并且你无法更改正在使用的 HTTP 方法,你可以更新你的表单,使用 PUT 方法代替

<%= form_for [ :update_name, @user ], method: :put do |f| %>

有关 PATCH 的更多信息以及为什么做出这种改变,请参阅 Rails 博客上的 这篇文章

13.1.1 关于媒体类型的说明

PATCH 动词的勘误表 指定应将 'diff' 媒体类型与 PATCH 一起使用。一种这样的格式是 JSON Patch。虽然 Rails 本身不支持 JSON Patch,但添加支持非常容易

# in your controller:
def update
  respond_to do |format|
    format.json do
      # perform a partial update
      @article.update params[:article]
    end

    format.json_patch do
      # perform sophisticated change
    end
  end
end
# config/initializers/json_patch.rb
Mime::Type.register "application/json-patch+json", :json_patch

由于 JSON Patch 是最近才成为 RFC,因此还没有很多优秀的 Ruby 库。Aaron Patterson 的 hana 就是这样一个 gem,但它并不完全支持规范中的最后几次更改。

13.2 Gemfile

Rails 4.0 从 Gemfile 中删除了 assets 组。升级时需要从 Gemfile 中删除该行。你应该更新应用程序文件(在 config/application.rb 中)

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

13.3 vendor/plugins

Rails 4.0 不再支持从 vendor/plugins 加载插件。你必须将所有插件替换为提取到 gem 并将它们添加到 Gemfile 中。如果你选择不将它们制作成 gem,你可以将它们移动到例如 lib/my_plugin/* 中,并在 config/initializers/my_plugin.rb 中添加一个适当的初始化器。

13.4 Active Record

  • Rails 4.0 从 Active Record 中删除了标识映射,这是由于 与关联之间存在一些不一致。如果你在应用程序中手动启用了它,你将必须删除以下不再有效的配置:config.active_record.identity_map

  • 集合关联中的 delete 方法现在可以接收 IntegerString 类型的参数作为记录 ID,除了记录本身,这与 destroy 方法非常类似。以前,对于这样的参数,它会引发 ActiveRecord::AssociationTypeMismatch。从 Rails 4.0 开始,delete 会自动尝试查找与给定 ID 匹配的记录,然后再删除它们。

  • 在 Rails 4.0 中,当重命名列或表时,相关的索引也会被重命名。如果你的迁移重命名了索引,则不再需要它们。

  • Rails 4.0 已将 serialized_attributesattr_readonly 更改为仅限类方法。你不应该使用实例方法,因为它现在已弃用。你应该将它们更改为使用类方法,例如 self.serialized_attributes 改为 self.class.serialized_attributes

  • 使用默认编码器时,将 nil 分配给序列化属性会将其保存到数据库中,作为 NULL,而不是将 nil 值传递给 YAML("--- \n...\n")。

  • Rails 4.0 已删除了 attr_accessibleattr_protected 功能,支持 Strong Parameters。你可以使用 Protected Attributes gem 来实现平滑的升级路径。

  • 如果你没有使用 Protected Attributes,你可以删除与该 gem 相关的任何选项,例如 whitelist_attributesmass_assignment_sanitizer 选项。

  • Rails 4.0 要求作用域使用可调用对象,例如 Proc 或 lambda

    scope :active, where(active: true)
    
    # becomes
    scope :active, -> { where active: true }
    
  • Rails 4.0 已弃用 ActiveRecord::Fixtures,支持 ActiveRecord::FixtureSet

  • Rails 4.0 已弃用 ActiveRecord::TestCase,支持 ActiveSupport::TestCase

  • Rails 4.0 已弃用旧式的基于哈希的查找器 API。这意味着以前接受“查找器选项”的方法不再这样做。例如,Book.find(:all, conditions: { name: '1984' }) 已被弃用,支持 Book.where(name: '1984')

  • find_by_...find_by_...! 之外的所有动态方法都已弃用。以下是如何处理这些更改

    • find_all_by_... 变为 where(...)
    • find_last_by_... 变为 where(...).last
    • scoped_by_... 变为 where(...)
    • find_or_initialize_by_... 变为 find_or_initialize_by(...)
    • find_or_create_by_... 变为 find_or_create_by(...)
  • 请注意,where(...) 返回的是一个关联,而不是像旧查找器那样返回一个数组。如果你需要一个 Array,请使用 where(...).to_a

  • 这些等效方法可能不会执行与以前实现相同的 SQL。

  • 要重新启用旧查找器,你可以使用 activerecord-deprecated_finders gem

  • Rails 4.0 已更改为默认情况下,has_and_belongs_to_many 关系的联接表会从第二个表名中去除公共前缀。任何现有的 has_and_belongs_to_many 关系,如果两个模型之间有公共前缀,必须使用 join_table 选项指定。例如

    class CatalogCategory < ActiveRecord::Base
      has_and_belongs_to_many :catalog_products, join_table: "catalog_categories_catalog_products"
    end
    
    class CatalogProduct < ActiveRecord::Base
      has_and_belongs_to_many :catalog_categories, join_table: "catalog_categories_catalog_products"
    end
    
  • 请注意,前缀也会考虑作用域,因此 Catalog::CategoryCatalog::ProductCatalog::CategoryCatalogProduct 之间的关系需要类似地更新。

13.5 Active Resource

Rails 4.0 将 Active Resource 提取到了自己的 gem 中。如果你仍然需要该功能,你可以在 Gemfile 中添加 Active Resource gem

13.6 Active Model

  • Rails 4.0 改变了 ActiveModel::Validations::ConfirmationValidator 中错误的附加方式。现在,当确认验证失败时,错误将附加到 :#{attribute}_confirmation 而不是 attribute

  • Rails 4.0 已将 ActiveModel::Serializers::JSON.include_root_in_json 的默认值更改为 false。现在,Active Model Serializers 和 Active Record 对象具有相同的默认行为。这意味着你可以在 config/initializers/wrap_parameters.rb 文件中注释或删除以下选项

    # Disable root element in JSON by default.
    # ActiveSupport.on_load(:active_record) do
    #   self.include_root_in_json = false
    # end
    

13.7 Action Pack

  • Rails 4.0 引入了 ActiveSupport::KeyGenerator,并将其用作生成和验证签名 Cookie(以及其他事项)的基础。如果你保留现有的 secret_token 并添加新的 secret_key_base,则使用 Rails 3.x 生成的现有签名 Cookie 将被透明地升级。

    # config/initializers/secret_token.rb
    Myapp::Application.config.secret_token = "existing secret token"
    Myapp::Application.config.secret_key_base = "new secret key base"
    

    请注意,你应该等到你的所有用户都迁移到 Rails 4.x 并且你确信不需要回滚到 Rails 3.x 时再设置 secret_key_base。这是因为在 Rails 4.x 中基于新的 secret_key_base 生成的签名 Cookie 与 Rails 3.x 不兼容。你可以在不设置新的 secret_key_base 的情况下保留现有的 secret_token,并忽略弃用警告,直到你确信你的升级在其他方面已经完成。

    如果你依赖外部应用程序或 JavaScript 能够读取 Rails 应用程序的签名会话 Cookie(或签名 Cookie ),则你不应该设置 secret_key_base,直到你将这些问题解耦。

  • 如果设置了secret_key_base,Rails 4.0 会对基于 Cookie 的会话内容进行加密。Rails 3.x 会对基于 Cookie 的会话内容进行签名,但不会加密。签名后的 Cookie 是“安全的”,因为它们经过验证,是由您的应用生成的,并且是防篡改的。但是,最终用户可以查看内容,对内容进行加密可以消除这种弊端/担忧,并且不会带来显著的性能损失。

    请阅读Pull Request #9978,了解有关迁移到加密会话 Cookie 的详细信息。

  • Rails 4.0 已删除ActionController::Base.asset_path选项。使用资产管道功能。

  • Rails 4.0 已弃用ActionController::Base.page_cache_extension选项。请改用ActionController::Base.default_static_extension

  • Rails 4.0 已从 Action Pack 中删除 Action 和 Page 缓存。您需要添加actionpack-action_caching gem 才能在控制器中使用caches_action,并添加actionpack-page_caching才能使用caches_page

  • Rails 4.0 已删除 XML 参数解析器。如果您需要此功能,则需要添加actionpack-xml_parser gem。

  • Rails 4.0 更改了默认layout查找集,使用符号或返回 nil 的 proc。要获得“无布局”行为,请返回 false 而不是 nil。

  • Rails 4.0 将默认 memcached 客户端从memcache-client更改为dalli。要升级,只需在您的Gemfile中添加gem "dalli"即可。

  • Rails 4.0 弃用了控制器中的dom_iddom_class方法(在视图中使用是可以的)。您需要在需要此功能的控制器中包含ActionView::RecordIdentifier模块。

  • Rails 4.0 弃用了link_to helper 的:confirm选项。您应该改用数据属性(例如data: { confirm: 'Are you sure?' })。此弃用也涉及基于此 helper 的 helper(例如link_to_iflink_to_unless)。

  • Rails 4.0 更改了assert_generatesassert_recognizesassert_routing的工作方式。现在所有这些断言都引发Assertion,而不是ActionController::RoutingError

  • 如果定义了冲突的命名路由,Rails 4.0 会引发ArgumentError。这可能是由显式定义的命名路由或resources方法触发的。以下是与名为example_path的路由冲突的两个示例

    get "one" => "test#example", as: :example
    get "two" => "test#example", as: :example
    
    resources :examples
    get "clashing/:id" => "test#example", as: :example
    

    在第一种情况下,您可以简单地避免对多个路由使用相同的名称。在第二种情况下,您可以使用resources方法提供的onlyexcept选项来限制创建的路由,如路由指南中所述。

  • Rails 4.0 还更改了绘制 unicode 字符路由的方式。现在您可以直接绘制 unicode 字符路由。如果您已经绘制了此类路由,则必须更改它们,例如

    get Rack::Utils.escape("こんにちは"), controller: "welcome", action: "index"
    

    变为

    get "こんにちは", controller: "welcome", action: "index"
    
  • Rails 4.0 要求使用match的路由必须指定请求方法。例如

    # Rails 3.x
    match "/" => "root#index"
    
    # becomes
    match "/" => "root#index", via: :get
    
    # or
    get "/" => "root#index"
    
  • Rails 4.0 已删除ActionDispatch::BestStandardsSupport中间件,<!DOCTYPE html>已根据https://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx触发标准模式,ChromeFrame 标头已移至config.action_dispatch.default_headers

    请记住,您还必须从应用程序代码中删除对中间件的所有引用,例如

    # Raise exception
    config.middleware.insert_before(Rack::Lock, ActionDispatch::BestStandardsSupport)
    

    还要检查您的环境设置,以查找config.action_dispatch.best_standards_support,如果存在,请删除它。

  • Rails 4.0 允许通过设置config.action_dispatch.default_headers来配置 HTTP 标头。默认值如下

    config.action_dispatch.default_headers = {
      "X-Frame-Options" => "SAMEORIGIN",
      "X-XSS-Protection" => "1; mode=block"
    }
    

    请注意,如果您的应用程序依赖于在<frame><iframe>中加载某些页面,则您可能需要将X-Frame-Options显式设置为ALLOW-FROM ...ALLOWALL

  • 在 Rails 4.0 中,预编译资产不再自动将非 JS/CSS 资产从vendor/assetslib/assets复制。Rails 应用程序和引擎开发人员应将这些资产放在app/assets中,或配置config.assets.precompile

  • 在 Rails 4.0 中,当操作不处理请求格式时,会引发ActionController::UnknownFormat。默认情况下,此异常由使用 406 Not Acceptable 进行响应来处理,但您现在可以覆盖它。在 Rails 3 中,始终返回 406 Not Acceptable。没有覆盖。

  • 在 Rails 4.0 中,当ParamsParser无法解析请求参数时,会引发一个通用的ActionDispatch::ParamsParser::ParseError异常。您需要救援此异常,而不是底层的MultiJson::DecodeError,例如

  • 在 Rails 4.0 中,当引擎安装在从 URL 前缀提供的应用程序中时,SCRIPT_NAME会正确嵌套。您不再需要设置default_url_options[:script_name]来解决被覆盖的 URL 前缀问题。

  • Rails 4.0 已弃用ActionController::Integration,转而使用ActionDispatch::Integration

  • Rails 4.0 已弃用ActionController::IntegrationTest,转而使用ActionDispatch::IntegrationTest

  • Rails 4.0 已弃用ActionController::PerformanceTest,转而使用ActionDispatch::PerformanceTest

  • Rails 4.0 已弃用ActionController::AbstractRequest,转而使用ActionDispatch::Request

  • Rails 4.0 已弃用ActionController::Request,转而使用ActionDispatch::Request

  • Rails 4.0 已弃用ActionController::AbstractResponse,转而使用ActionDispatch::Response

  • Rails 4.0 已弃用ActionController::Response,转而使用ActionDispatch::Response

  • Rails 4.0 已弃用ActionController::Routing,转而使用ActionDispatch::Routing

13.8 Active Support

Rails 4.0 删除了jERB::Util#json_escape的别名,因为j已经用于ActionView::Helpers::JavaScriptHelper#escape_javascript

13.8.1 Cache

Rails 3.x 和 4.0 之间的缓存方法有所不同。您应该更改缓存命名空间并使用冷缓存进行推广。

13.9 Helpers 加载顺序

在 Rails 4.0 中,从多个目录加载 helper 的顺序已更改。以前,它们会被收集,然后按字母顺序排序。升级到 Rails 4.0 后,helper 会保留加载目录的顺序,并且只会在每个目录内按字母顺序排序。除非您显式使用helpers_path参数,否则此更改只会影响从引擎加载 helper 的方式。如果您依赖于排序,则应在升级后检查是否可以使用正确的 method。如果您想更改引擎的加载顺序,可以使用config.railties_order= method。

13.10 Active Record Observer 和 Action Controller Sweeper

ActiveRecord::ObserverActionController::Caching::Sweeper已被提取到rails-observers gem 中。如果您需要这些功能,则需要添加rails-observers gem。

13.11 sprockets-rails

  • assets:precompile:primaryassets:precompile:all已被删除。请改用assets:precompile
  • config.assets.compress选项应更改为config.assets.js_compressor,例如

    config.assets.js_compressor = :uglifier
    

13.12 sass-rails

  • 带有两个参数的asset-url已弃用。例如:asset-url("rails.png", image)变为asset-url("rails.png")

14 从 Rails 3.1 升级到 Rails 3.2

如果您的应用程序当前使用的是任何低于 3.1.x 版本的 Rails,则应升级到 Rails 3.1,然后再尝试更新到 Rails 3.2。

以下更改适用于将您的应用程序升级到最新 3.2.x 版本的 Rails。

14.1 Gemfile

对您的Gemfile进行以下更改。

gem "rails", "3.2.21"

group :assets do
  gem "sass-rails",   "~> 3.2.6"
  gem "coffee-rails", "~> 3.2.2"
  gem "uglifier",     ">= 1.0.3"
end

14.2 config/environments/development.rb

您应该在开发环境中添加一些新的配置设置

# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict

# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
config.active_record.auto_explain_threshold_in_seconds = 0.5

14.3 config/environments/test.rb

mass_assignment_sanitizer配置设置也应该添加到config/environments/test.rb

# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict

14.4 vendor/plugins

Rails 3.2 弃用了vendor/plugins,Rails 4.0 将完全删除它们。虽然在 Rails 3.2 升级过程中这不是必需的,但您可以开始通过将插件提取到 gem 并将其添加到Gemfile中来替换它们。如果您选择不将其转换为 gem,则可以将其移动到,例如,lib/my_plugin/*,并在config/initializers/my_plugin.rb中添加一个适当的初始化程序。

14.5 Active Record

belongs_to中已删除:dependent => :restrict选项。如果您想在存在任何关联对象时阻止删除对象,则可以设置:dependent => :destroy,并在从任何关联对象的 destroy 回调中检查关联是否存在后返回false

15 从 Rails 3.0 升级到 Rails 3.1

如果您的应用程序当前使用的是任何低于 3.0.x 版本的 Rails,则应升级到 Rails 3.0,然后再尝试更新到 Rails 3.1。

以下更改适用于将您的应用程序升级到 Rails 3.1.12,它是最后一个 3.1.x 版本的 Rails。

15.1 Gemfile

对您的Gemfile进行以下更改。

gem "rails", "3.1.12"
gem "mysql2"

# Needed for the new asset pipeline
group :assets do
  gem "sass-rails",   "~> 3.1.7"
  gem "coffee-rails", "~> 3.1.1"
  gem "uglifier",     ">= 1.0.3"
end

# jQuery is the default JavaScript library in Rails 3.1
gem "jquery-rails"

15.2 config/application.rb

资产管道需要以下添加

config.assets.enabled = true
config.assets.version = "1.0"

如果您的应用程序使用“/assets”路由来访问资源,您可能想要更改用于资产的前缀,以避免冲突

# Defaults to '/assets'
config.assets.prefix = "/asset-files"

15.3 config/environments/development.rb

删除 RJS 设置config.action_view.debug_rjs = true

如果您启用了资产管道,请添加以下设置

# Do not compress assets
config.assets.compress = false

# Expands the lines which load the assets
config.assets.debug = true

15.4 config/environments/production.rb

同样,以下大多数更改都与资产管道有关。您可以在资产管道指南中了解更多信息。

# Compress JavaScripts and CSS
config.assets.compress = true

# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false

# Generate digests for assets URLs
config.assets.digest = true

# Defaults to Rails.root.join("public/assets")
# config.assets.manifest = YOUR_PATH

# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( admin.js admin.css )

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true

15.5 config/environments/test.rb

您可以通过对测试环境进行以下添加来帮助测试性能

# Configure static asset server for tests with Cache-Control for performance
config.public_file_server.enabled = true
config.public_file_server.headers = {
  "Cache-Control" => "public, max-age=3600"
}

15.6 config/initializers/wrap_parameters.rb

如果要将参数包装到嵌套哈希中,请添加此文件,内容如下。在新应用程序中默认启用此功能。

# Be sure to restart your server when you modify this file.
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.

# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: [:json]
end

# Disable root element in JSON by default.
ActiveSupport.on_load(:active_record) do
  self.include_root_in_json = false
end

15.7 config/initializers/session_store.rb

您需要将会话密钥更改为新的密钥,或者删除所有会话

# in config/initializers/session_store.rb
AppName::Application.config.session_store :cookie_store, key: "SOMETHINGNEW"

$ bin/rake db:sessions:clear

15.8 删除视图中对资产 helper 引用中的 :cache 和 :concat 选项

  • 使用资产管道时,不再使用 :cache 和 :concat 选项,请从您的视图中删除这些选项。


返回顶部