Zeitwerk:将 engine/gem 目录添加到父 Rails 应用程序的自动加载路径

Toc*_*cko 3 ruby rubygems ruby-on-rails zeitwerk

我正在尝试在现有的旧 Gem (Rails::Engine) 中切换到 Zeitwerk。到目前为止,所有文件都是手动require编辑autoload的。另外,引擎的 lib 文件夹已通过config.autoload_paths += paths["lib"].to_ain添加到 autoload_paths 中class MyEngine < Rails::Engine

通过自述文件中描述的方式切换到使用 Zeitwerk 效果很好:

require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
.
. --> more project specific stuff here
.
loader.setup # ready!
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好!现在我想在 Rails 应用程序中使用 Gem,并将引擎的目录添加到 Rails 应用程序的 autoload_path 中。以前通过上述方法效果很好config.autoload_paths。如果我现在执行此操作,则会失败并显示以下错误消息:

Zeitwerk::Error:
  loader

#<Zeitwerk::Loader:0x00000001094d4bd0
...

wants to manage directory /gems/<NameOfGem>/lib, which is already managed by

#<Zeitwerk::Loader:0x0000000106b2d728
...
Run Code Online (Sandbox Code Playgroud)

将引擎的 lib 目录添加到 Rails 应用程序的自动加载路径的正确方法是什么?

谢谢你!

Ale*_*lex 5

Rails设置了两个加载器main并且once

Rails.autoloaders.main
Rails.autoloaders.once
Run Code Online (Sandbox Code Playgroud)

这些只是 的实例Zeitwerk::LoaderRails还为您提供了一个配置来将根目录添加到这些加载器:

config.autoload_paths         # main
config.autoload_once_paths    # once
Run Code Online (Sandbox Code Playgroud)

当 gem 的lib目录通过以下配置之一添加到自动加载时,lib将成为根目录:

# config.autoload_paths += paths["lib"].to_a

>> Rails.autoloaders.main.root_dirs
=> 
...                    
 "/home/alex/code/stackoverflow/my_engine/lib"=>Object,
...
Run Code Online (Sandbox Code Playgroud)

当调用 gem 中的类时,zeitwerk使用注册的加载器来查找并加载与该类对应的文件。

如果 gem 设置了自己的加载器:

require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup
Run Code Online (Sandbox Code Playgroud)

的另一个实例Zeitwerk::Loader是用它自己的根目录创建的:

>> Zeitwerk::Registry.loaders.detect { |z| z.tag == "my_engine" }
=> 
#<Zeitwerk::GemLoader:0x00007fe5e53e0f80
...
 @root_dirs={"/home/alex/code/stackoverflow/my_engine/lib"=>Object},
...

# NOTE: these are the two loaders registered by rails
>> Zeitwerk::Registry.loaders.select { |z| z.tag =~ /rails/ }.count
=> 2
Run Code Online (Sandbox Code Playgroud)

Zeitwerk不允许两个加载程序拥有共享目录,并会引发一个错误,显示两个冲突的加载程序。

因为 gem 是Rails::Engine,所以最好的选择是让Rails管理zeitwerk加载器并删除Zeitwerk::Loader.for_gem设置。

# only use rails config
config.autoload_paths += paths["lib"].to_a
Run Code Online (Sandbox Code Playgroud)

另一方面,gem 加载器已经设置完毕,不需要config.autoload_paths 。

require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup
Run Code Online (Sandbox Code Playgroud)

更新

# Use rails loaders
# config.autoload_path          .->  Zeitwerk::Loader(@tag=rails.main)
# config.autoload_once_path     |->  Zeitwerk::Loader(@tag=rails.once)
#                               |
# Or create a new loader        |
# Zeitwerk::Loader.for_gem      |->  Zeitwerk::GemLoader(@tag=my_engine)
#                               |
# my_engine/lib can only be in one of these
Run Code Online (Sandbox Code Playgroud)

Zeitwerk负责加载和重新加载。Rails只是这里的另一颗宝石。

如果您不使用 Rails config,Zeitwerk将通过 查找Zeitwerk::GemLoader(@tag=my_engine)gem 创建的文件。

如果您使用rails配置,ZeitwerkZeitwerk::Loader(@tag=rails.main)将通过rails创建的文件找到文件(使得GemLoader变得不必要)。

如果lib是任何现有加载器中的根目录,则不需要对lib目录中的文件有任何要求或自动加载。除了Zeitwerk启动之前需要的东西,例如lib/my_engine/engine.rb中的MyEngine::Engine

  • 这是一个很好的答案。 (2认同)