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 应用程序的自动加载路径的正确方法是什么?
谢谢你!
Rails设置了两个加载器main并且once:
Rails.autoloaders.main
Rails.autoloaders.once
Run Code Online (Sandbox Code Playgroud)
这些只是 的实例Zeitwerk::Loader。Rails还为您提供了一个配置来将根目录添加到这些加载器:
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。