更多内容请访问 rubyonrails.org:

1 介绍

本指南介绍了 Rails 应用程序中的自动加载、重新加载和急切加载。

在普通 Ruby 程序中,您显式加载定义要使用的类和模块的文件。例如,以下控制器引用了 `ApplicationController` 和 `Post`,并且您通常会为它们发出 `require` 调用

# DO NOT DO THIS.
require "application_controller"
require "post"
# DO NOT DO THIS.

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Rails 应用程序并非如此,在 Rails 应用程序中,应用程序类和模块无需 `require` 调用即可在任何地方使用。

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Rails 会在需要时为您自动加载它们。这得益于 Rails 代表您设置的几个 Zeitwerk 加载程序,它们提供了自动加载、重新加载和急切加载。

另一方面,这些加载程序不管理任何其他内容。特别是,它们不管理 Ruby 标准库、gem 依赖项、Rails 组件本身,甚至(默认情况下)不管理应用程序 `lib` 目录。该代码必须像往常一样加载。

2 项目结构

在 Rails 应用程序中,文件名必须与其定义的常量匹配,目录充当命名空间。

例如,文件 `app/helpers/users_helper.rb` 应该定义 `UsersHelper`,文件 `app/controllers/admin/payments_controller.rb` 应该定义 `Admin::PaymentsController`。

默认情况下,Rails 配置 Zeitwerk 使用 `String#camelize` 来改变文件名。例如,它预期 `app/controllers/users_controller.rb` 定义常量 `UsersController`,因为这是 `“users_controller”.camelize` 返回的值。

下面的自定义词形变化部分介绍了覆盖此默认设置的方法。

请查看 Zeitwerk 文档 以获取更多详细信息。

3 config.autoload_paths

我们将要自动加载和(可选)重新加载其内容的应用程序目录列表称为自动加载路径。例如,`app/models`。这些目录代表根命名空间:`Object`。

自动加载路径在 Zeitwerk 文档中被称为根目录,但本指南中将保留“自动加载路径”。

在自动加载路径内,文件名必须与其定义的常量匹配,如 此处 所述。

默认情况下,应用程序的自动加载路径包括应用程序启动时存在的 `app` 的所有子目录——除了 `assets`、`javascript` 和 `views`——以及它可能依赖的引擎的自动加载路径。

例如,如果 `UsersHelper` 在 `app/helpers/users_helper.rb` 中实现,则该模块是可自动加载的,您不需要(也不应该编写) `require` 调用来加载它

$ bin/rails runner 'p UsersHelper'
UsersHelper

Rails 会自动将 `app` 下的自定义目录添加到自动加载路径中。例如,如果您的应用程序有 `app/presenters`,您无需配置任何内容即可自动加载演示者;它可以开箱即用。

可以通过将内容推送到 `config.autoload_paths`(在 `config/application.rb` 或 `config/environments/*.rb` 中)来扩展默认自动加载路径数组。例如

module MyApplication
  class Application < Rails::Application
    config.autoload_paths << "#{root}/extras"
  end
end

此外,引擎可以在引擎类的主体中以及它们自己的 `config/environments/*.rb` 中进行推送。

请不要修改 `ActiveSupport::Dependencies.autoload_paths`;更改自动加载路径的公共接口是 `config.autoload_paths`。

您无法在应用程序启动时自动加载自动加载路径中的代码。特别是,不能直接在 `config/initializers/*.rb` 中执行此操作。请查看下面的 应用程序启动时的自动加载,了解有效的方法。

自动加载路径由 `Rails.autoloaders.main` 自动加载器管理。

4 config.autoload_lib(ignore:)

默认情况下,`lib` 目录不属于应用程序或引擎的自动加载路径。

配置方法 `config.autoload_lib` 将 `lib` 目录添加到 `config.autoload_paths` 和 `config.eager_load_paths` 中。它必须从 `config/application.rb` 或 `config/environments/*.rb` 调用,并且引擎无法使用它。

通常,`lib` 有不应该由自动加载器管理的子目录。请将它们相对于 `lib` 的名称传递给必需的 `ignore` 关键字参数。例如

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

为什么?虽然 `assets` 和 `tasks` 与常规 Ruby 代码共享 `lib` 目录,但它们的内容并非旨在重新加载或急切加载。

`ignore` 列表应包含所有不包含 `rb` 扩展名的文件,或者不应该重新加载或急切加载的 `lib` 子目录。例如,

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

`config.autoload_lib` 在 7.1 之前不可用,但只要应用程序使用 Zeitwerk,您仍然可以模拟它

# config/application.rb
module MyApp
  class Application < Rails::Application
    lib = root.join("lib")

    config.autoload_paths << lib
    config.eager_load_paths << lib

    Rails.autoloaders.main.ignore(
      lib.join("assets"),
      lib.join("tasks"),
      lib.join("generators")
    )

    # ...
  end
end

5 config.autoload_once_paths

您可能希望能够自动加载类和模块,而无需重新加载它们。`autoload_once_paths` 配置存储可自动加载但不会重新加载的代码。

默认情况下,此集合为空,但您可以通过将内容推送到 `config.autoload_once_paths` 来扩展它。您可以在 `config/application.rb` 或 `config/environments/*.rb` 中执行此操作。例如

module MyApplication
  class Application < Rails::Application
    config.autoload_once_paths << "#{root}/app/serializers"
  end
end

此外,引擎可以在引擎类的主体中以及它们自己的 `config/environments/*.rb` 中进行推送。

如果将 `app/serializers` 推送到 `config.autoload_once_paths`,Rails 将不再将此目录视为自动加载路径,尽管它是在 `app` 下的自定义目录。此设置将覆盖该规则。

这对于在重新加载后会缓存到其他位置的类和模块至关重要,例如 Rails 框架本身。

例如,Active Job 序列化器存储在 Active Job 内

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

当应用程序重新加载时,Active Job 本身不会被重新加载,只有自动加载路径中的应用程序和引擎代码会被重新加载。

使 MoneySerializer 可重新加载会令人困惑,因为重新加载已编辑的版本对存储在 Active Job 中的该类对象没有影响。实际上,如果 MoneySerializer 可重新加载,从 Rails 7 开始,这样的初始化器将引发 NameError 错误。

另一个用例是当引擎装饰框架类时。

initializer "decorate ActionController::Base" do
  ActiveSupport.on_load(:action_controller_base) do
    include MyDecoration
  end
end

在这种情况下,初始化器运行时存储在 MyDecoration 中的模块对象成为 ActionController::Base 的祖先,重新加载 MyDecoration 毫无意义,它不会影响该祖先链。

来自一次性自动加载路径的类和模块可以在 config/initializers 中自动加载。因此,使用该配置,这将起作用

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

从技术上讲,您可以在运行 :bootstrap_hook 之后的所有初始化器中自动加载由 once 自动加载器管理的类和模块。

一次性自动加载路径由 Rails.autoloaders.once 管理。

6 config.autoload_lib_once(ignore:)

方法 config.autoload_lib_onceconfig.autoload_lib 类似,只是它将 lib 添加到 config.autoload_once_paths 而不是 config.autoload_paths。它必须从 config/application.rbconfig/environments/*.rb 调用,并且引擎无法使用它。

通过调用 config.autoload_lib_once,即使从应用程序初始化器中,也可以自动加载 lib 中的类和模块,但不会重新加载。

config.autoload_lib_once 在 7.1 之前不可用,但只要应用程序使用 Zeitwerk,您仍然可以模拟它。

# config/application.rb
module MyApp
  class Application < Rails::Application
    lib = root.join("lib")

    config.autoload_once_paths << lib
    config.eager_load_paths << lib

    Rails.autoloaders.once.ignore(
      lib.join("assets"),
      lib.join("tasks"),
      lib.join("generators")
    )

    # ...
  end
end

7 重新加载

如果自动加载路径中的应用程序文件发生更改,Rails 会自动重新加载类和模块。

更准确地说,如果 Web 服务器正在运行并且应用程序文件已修改,Rails 会在处理下一个请求之前卸载由 main 自动加载器管理的所有自动加载常量。这样,该请求期间使用的应用程序类或模块将再次自动加载,从而获取文件系统中它们当前的实现。

可以启用或禁用重新加载。控制此行为的设置是 config.enable_reloading,默认情况下在 development 模式下为 true,在 production 模式下默认情况下为 false。为了向后兼容,Rails 还支持 config.cache_classes,它等效于 !config.enable_reloading

Rails 默认情况下使用事件文件监视器来检测文件更改。可以将其配置为改为通过遍历自动加载路径来检测文件更改。这由 config.file_watcher 设置控制。

在 Rails 控制台中,无论 config.enable_reloading 的值如何,都没有活动的文件监视器。这是因为,通常,在控制台会话的中间重新加载代码会令人困惑。与单个请求类似,您通常希望控制台会话由一致且不更改的应用程序类和模块集提供服务。

但是,您可以通过执行 reload! 来强制在控制台中重新加载。

irb(main):001:0> User.object_id
=> 70136277390120
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> User.object_id
=> 70136284426020

如您所见,在重新加载后,存储在 User 常量中的类对象是不同的。

7.1 重新加载和陈旧对象

了解 Ruby 没有真正重新加载内存中类和模块的方法,并将其反映在它们已使用的所有位置非常重要。从技术上讲,“卸载”User 类意味着通过 Object.send(:remove_const, "User") 删除 User 常量。

例如,查看此 Rails 控制台会话

irb> joe = User.new
irb> reload!
irb> alice = User.new
irb> joe.class == alice.class
=> false

joe 是原始 User 类的实例。当发生重新加载时,User 常量将评估为不同的重新加载的类。alice 是新加载的 User 的实例,但 joe 不是 - 他的类是陈旧的。您可以再次定义 joe,启动一个 IRB 子会话,或者只启动一个新的控制台,而不是调用 reload!

您可能会遇到此问题陷阱的另一种情况是在未重新加载的位置对可重新加载的类进行子类化

# lib/vip_user.rb
class VipUser < User
end

如果 User 被重新加载,由于 VipUser 未被重新加载,因此 VipUser 的超类是原始的陈旧类对象。

最重要的是:不要缓存可重新加载的类或模块

8 应用程序启动时的自动加载

在启动时,应用程序可以从一次性自动加载路径自动加载,这些路径由 once 自动加载器管理。请检查上面 config.autoload_once_paths 部分。

但是,您不能从由 main 自动加载器管理的自动加载路径自动加载。这适用于 config/initializers 中的代码以及应用程序或引擎初始化器。

为什么?初始化器只在应用程序启动时运行一次。它们不会在重新加载时再次运行。如果初始化器使用了可重新加载的类或模块,对它们的编辑将不会反映在该初始代码中,从而变得陈旧。因此,在初始化期间引用可重新加载的常量是不允许的。

让我们看看该怎么做。

8.1 用例 1:在启动时加载可重新加载的代码

8.1.1 在启动时和每次重新加载时自动加载

假设 ApiGateway 是一个可重新加载的类,您需要在应用程序启动时配置其端点

# config/initializers/api_gateway_setup.rb
ApiGateway.endpoint = "https://example.com" # NameError

初始化器不能引用可重新加载的常量,您需要将其包装在一个 to_prepare 块中,该块在启动时和每次重新加载后运行

# config/initializers/api_gateway_setup.rb
Rails.application.config.to_prepare do
  ApiGateway.endpoint = "https://example.com" # CORRECT
end

由于历史原因,此回调可能会运行两次。它执行的代码必须是幂等的。

8.1.2 仅在启动时自动加载

可重新加载的类和模块也可以在 after_initialize 块中自动加载。这些在启动时运行,但在重新加载时不会再次运行。在某些特殊情况下,这可能是您想要的。

预检是此用例。

# config/initializers/check_admin_presence.rb
Rails.application.config.after_initialize do
  unless Role.where(name: "admin").exists?
    abort "The admin role is not present, please seed the database."
  end
end

8.2 用例 2:在启动时加载保持缓存的代码

某些配置采用类或模块对象,并将它们存储在不会被重新加载的位置。重要的是这些不是可重新加载的,因为编辑不会反映在这些缓存的陈旧对象中。

一个例子是中间件

config.middleware.use MyApp::Middleware::Foo

当您重新加载时,中间件堆栈不会受到影响,因此 MyApp::Middleware::Foo 可重新加载会令人困惑。其实现中的更改不会产生任何影响。

另一个例子是 Active Job 序列化器

# config/initializers/custom_serializers.rb
Rails.application.config.active_job.custom_serializers << MoneySerializer

无论 MoneySerializer 在初始化期间评估为多少,都会将其推送到自定义序列化器中,并且该对象在重新加载时会保留在那里。

另一个例子是通过包含模块来装饰框架类的 railties 或引擎。例如,turbo-rails 以这种方式装饰 ActiveRecord::Base

initializer "turbo.broadcastable" do
  ActiveSupport.on_load(:active_record) do
    include Turbo::Broadcastable
  end
end

这将一个模块对象添加到 ActiveRecord::Base 的祖先链中。如果重新加载,Turbo::Broadcastable 中的更改不会产生任何影响,祖先链将仍然具有原始链。

推论:这些类或模块不能重新加载

组织这些文件的惯用方式是将它们放在 lib 目录中,并在需要时使用 require 加载它们。例如,如果应用程序在 lib/middleware 中有自定义中间件,请在配置它之前发出一个常规的 require 调用

require "middleware/my_middleware"
config.middleware.use MyMiddleware

此外,如果 lib 位于自动加载路径中,请配置自动加载器以忽略该子目录

# config/application.rb
config.autoload_lib(ignore: %w(assets tasks ... middleware))

因为您自己加载这些文件。

如上所述,另一个选择是将定义它们的目录放在一次性自动加载路径中并自动加载。请检查 有关 config.autoload_once_paths 的部分 以了解更多详细信息。

8.3 用例 3:为引擎配置应用程序类

假设引擎与可重新加载的应用程序类一起使用,该类对用户进行建模,并具有一个配置点

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = User # NameError
end

为了与可重新加载的应用程序代码很好地协同工作,引擎需要应用程序来配置该类的名称

# config/initializers/my_engine.rb
MyEngine.configure do |config|
  config.user_model = "User" # OK
end

然后,在运行时,config.user_model.constantize 会为您提供当前的类对象。

9 渴望加载

在类似生产的环境中,通常最好在应用程序启动时加载所有应用程序代码。渴望加载将所有内容放入内存中,以便立即准备服务请求,并且它也对 CoW 友好。

渴望加载由标志 config.eager_load 控制,默认情况下在除 production 之外的所有环境中都禁用。当执行 Rake 任务时,config.eager_load 会被 config.rake_eager_load 覆盖,后者默认情况下为 false。因此,默认情况下,在生产环境中,Rake 任务不会渴望加载应用程序。

文件渴望加载的顺序是未定义的。

在渴望加载期间,Rails 调用 Zeitwerk::Loader.eager_load_all。这确保了由 Zeitwerk 管理的所有 gem 依赖项也都被渴望加载。

10 单表继承

单表继承与延迟加载不兼容:Active Record 必须了解 STI 层次结构才能正常工作,但当延迟加载时,类仅按需加载!

为了解决这种根本的不匹配,我们需要预加载 STI。有一些方法可以实现这一点,但权衡不同。让我们来看看它们。

10.1 选项 1:启用渴望加载

预加载 STI 的最简单方法是通过设置来启用渴望加载

config.eager_load = true

config/environments/development.rbconfig/environments/test.rb 中。

这很简单,但可能很昂贵,因为它会在启动时和每次重新加载时渴望加载整个应用程序。不过,对于小型应用程序来说,这种权衡可能是值得的。

10.2 选项 2:预加载一个折叠的目录

将定义层次结构的文件存储在专用目录中,从概念上讲也是有意义的。该目录不打算表示命名空间,其唯一目的是对 STI 进行分组

app/models/shapes/shape.rb
app/models/shapes/circle.rb
app/models/shapes/square.rb
app/models/shapes/triangle.rb

在这个例子中,我们仍然希望 `app/models/shapes/circle.rb` 定义 `Circle`,而不是 `Shapes::Circle`。这可能是您个人偏好,为了保持简单,并避免现有代码库的重构。Zeitwerk 的 折叠 功能允许我们做到这一点

# config/initializers/preload_stis.rb

shapes = "#{Rails.root}/app/models/shapes"
Rails.autoloaders.main.collapse(shapes) # Not a namespace.

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir(shapes)
  end
end

在这个选项中,我们会在启动时预加载这几个文件,即使没有使用 STI 也会重新加载它们。但是,除非您的应用程序有很多 STI,否则这不会产生任何可衡量的影响。

方法 `Zeitwerk::Loader#eager_load_dir` 是在 Zeitwerk 2.6.2 中添加的。对于旧版本,您仍然可以列出 `app/models/shapes` 目录并调用 `require_dependency` 来加载它的内容。

如果模型从 STI 中添加、修改或删除,则重新加载按预期工作。但是,如果应用程序中添加了新的独立 STI 层次结构,您需要编辑初始化器并重新启动服务器。

10.3 选项 3:预加载常规目录

类似于上一个选项,但目录应该是命名空间。也就是说,`app/models/shapes/circle.rb` 预期定义 `Shapes::Circle`。

对于这个选项,初始化器相同,只是没有配置折叠

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    Rails.autoloaders.main.eager_load_dir("#{Rails.root}/app/models/shapes")
  end
end

相同的权衡。

10.4 选项 4:从数据库预加载类型

在这个选项中,我们不需要以任何方式组织文件,但会访问数据库

# config/initializers/preload_stis.rb

unless Rails.application.config.eager_load
  Rails.application.config.to_prepare do
    types = Shape.unscoped.select(:type).distinct.pluck(:type)
    types.compact.each(&:constantize)
  end
end

即使表没有所有类型,STI 也会正常工作,但像 `subclasses` 或 `descendants` 这样的方法不会返回缺少的类型。

如果模型从 STI 中添加、修改或删除,则重新加载按预期工作。但是,如果应用程序中添加了新的独立 STI 层次结构,您需要编辑初始化器并重新启动服务器。

11 自定义词形变化

默认情况下,Rails 使用 `String#camelize` 来确定给定文件或目录名称应该定义哪个常量。例如,`posts_controller.rb` 应该定义 `PostsController`,因为这是 `“posts_controller”.camelize` 返回的值。

某些特定文件或目录名称可能无法按预期进行词形变化。例如,`html_parser.rb` 预期默认定义 `HtmlParser`。如果您希望类是 `HTMLParser` 怎么办?有几种方法可以自定义这一点。

最简单的方法是定义首字母缩略词

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

这样做会影响 Active Support 如何全局进行词形变化。在某些应用程序中可能没问题,但您还可以自定义如何独立于 Active Support 对单个基本名称进行词形变化,方法是将一组覆盖传递给默认的词形变化器

Rails.autoloaders.each do |autoloader|
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

但是,这种技术仍然依赖于 `String#camelize`,因为这是默认的词形变化器用作回退的方法。如果您更愿意完全不依赖于 Active Support 词形变化器,并且完全控制词形变化器,请将词形变化器配置为 `Zeitwerk::Inflector` 的实例

Rails.autoloaders.each do |autoloader|
  autoloader.inflector = Zeitwerk::Inflector.new
  autoloader.inflector.inflect(
    "html_parser" => "HTMLParser",
    "ssl_error"   => "SSLError"
  )
end

没有全局配置可以影响这些实例;它们是确定性的。

您甚至可以定义自定义词形变化器以实现完全灵活性。请查看 Zeitwerk 文档 以获取更多详细信息。

11.1 词形变化自定义应该放在哪里?

如果应用程序不使用 `once` 自动加载器,则上面的代码片段可以放在 `config/initializers` 中。例如,对于 Active Support 用例,`config/initializers/inflections.rb`,或者对于其他用例,`config/initializers/zeitwerk.rb`。

使用 `once` 自动加载器的应用程序必须将此配置从 `config/application.rb` 中的应用程序类主体中移动或加载,因为 `once` 自动加载器在引导过程的早期使用词形变化器。

12 自定义命名空间

如上所述,自动加载路径代表顶级命名空间:`Object`。

例如,让我们考虑 `app/services`。默认情况下不会生成此目录,但如果它存在,Rails 会自动将其添加到自动加载路径中。

默认情况下,文件 `app/services/users/signup.rb` 预期定义 `Users::Signup`,但如果您希望整个子树位于 `Services` 命名空间下怎么办?好吧,使用默认设置,这可以通过创建子目录来实现:`app/services/services`。

但是,根据您的喜好,这可能感觉不对。您可能更愿意让 `app/services/users/signup.rb` 只定义 `Services::Users::Signup`。

Zeitwerk 支持 自定义根命名空间 来解决这种情况,您可以自定义 `main` 自动加载器来实现这一点

# config/initializers/autoloading.rb

# The namespace has to exist.
#
# In this example we define the module on the spot. Could also be created
# elsewhere and its definition loaded here with an ordinary `require`. In
# any case, `push_dir` expects a class or module object.
module Services; end

Rails.autoloaders.main.push_dir("#{Rails.root}/app/services", namespace: Services)

Rails < 7.1 不支持此功能,但您仍然可以在同一个文件中添加此额外的代码并使其正常工作

# Additional code for applications running on Rails < 7.1.
app_services_dir = "#{Rails.root}/app/services" # has to be a string
ActiveSupport::Dependencies.autoload_paths.delete(app_services_dir)
Rails.application.config.watchable_dirs[app_services_dir] = [:rb]

自定义命名空间也支持 `once` 自动加载器。但是,由于它是在引导过程的早期设置的,因此配置不能在应用程序初始化器中完成。相反,请将其放在 `config/application.rb` 中,例如。

13 自动加载和引擎

引擎在父应用程序的上下文中运行,它们的代码由父应用程序自动加载、重新加载和预加载。如果应用程序在 `zeitwerk` 模式下运行,则引擎代码由 `zeitwerk` 模式加载。如果应用程序在 `classic` 模式下运行,则引擎代码由 `classic` 模式加载。

当 Rails 启动时,引擎目录会添加到自动加载路径中,从自动加载器的角度来看,没有区别。自动加载器的主要输入是自动加载路径,它们是属于应用程序源树还是引擎源树无关紧要。

例如,此应用程序使用 Devise

$ bin/rails runner 'pp ActiveSupport::Dependencies.autoload_paths'
[".../app/controllers",
 ".../app/controllers/concerns",
 ".../app/helpers",
 ".../app/models",
 ".../app/models/concerns",
 ".../gems/devise-4.8.0/app/controllers",
 ".../gems/devise-4.8.0/app/helpers",
 ".../gems/devise-4.8.0/app/mailers"]

如果引擎控制其父应用程序的自动加载模式,则引擎可以像往常一样编写。

但是,如果引擎支持 Rails 6 或 Rails 6.1 并且不控制其父应用程序,则它必须准备好以 `classic` 或 `zeitwerk` 模式运行。需要注意的事项

  1. 如果 `classic` 模式需要 `require_dependency` 调用来确保某个常量在某个时候加载,请编写它。虽然 `zeitwerk` 不需要它,但它不会造成伤害,它在 `zeitwerk` 模式下也能工作。

  2. `classic` 模式对常量名称使用下划线(“User” -> “user.rb”),而 `zeitwerk` 模式对文件名使用驼峰式命名法(“user.rb” -> “User”)。在大多数情况下,它们会一致,但如果有一系列连续的大写字母,例如 “HTMLParser”,则它们不会一致。保持兼容的最简单方法是避免使用这种名称。在这种情况下,选择 “HtmlParser”。

  3. 在 `classic` 模式下,文件 `app/model/concerns/foo.rb` 允许同时定义 `Foo` 和 `Concerns::Foo`。在 `zeitwerk` 模式下,只有一个选项:它必须定义 `Foo`。为了保持兼容,请定义 `Foo`。

14 测试

14.1 手动测试

任务 `zeitwerk:check` 检查项目树是否遵循预期的命名约定,它非常适合手动检查。例如,如果您要从 `classic` 模式迁移到 `zeitwerk` 模式,或者您要修复某些问题

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

根据应用程序配置,可能会出现其他输出,但最后一个 “All is good!” 是您要寻找的。

14.2 自动化测试

建议在测试套件中验证项目是否正确预加载。

这涵盖了 Zeitwerk 命名约定和其他可能的错误情况。请查看 关于测试预加载的章节,该章节位于 测试 Rails 应用程序 指南中。

15 故障排除

跟踪加载器活动的最佳方法是检查它们的活动。

最简单的方法是包含

Rails.autoloaders.log!

在 `config/application.rb` 中,在加载框架默认值之后。这将打印跟踪信息到标准输出。

如果您更愿意将日志记录到文件,请改用以下配置

Rails.autoloaders.logger = Logger.new("#{Rails.root}/log/autoloading.log")

当 `config/application.rb` 执行时,Rails 日志记录器还不可用。如果您更愿意使用 Rails 日志记录器,请在初始化器中配置此设置

# config/initializers/log_autoloaders.rb
Rails.autoloaders.logger = Rails.logger

16 Rails.autoloaders

管理应用程序的 Zeitwerk 实例位于

Rails.autoloaders.main
Rails.autoloaders.once

谓词

Rails.autoloaders.zeitwerk_enabled?

仍然在 Rails 7 应用程序中可用,并且返回 `true`。



返回顶部