使用 Zeitwerk 自动加载器在 Rails 6 中命名服务对象

Namespacing service objects in Rails 6 with Zeitwerk autoloader

提问人:phoxley 提问时间:8/5/2020 更新时间:7/20/2022 访问量:2245

问:

Rails 6 切换到 Zeitwerk 作为默认的自动加载器。Zeitwerk 将加载 /app 文件夹中的所有文件,无需命名空间。这意味着,现在可以直接调用 app/services/demo/test_service.rb 中的 TestService 服务对象,例如 .TestService.new().call

然而,命名空间有助于在更复杂的 rails 应用程序中组织对象,例如 API::UsersController,或者对于我们使用 Registration::CreateAccount、Registration::AddDemoData 等的服务。

rails 指南建议的一种解决方案是从 application.rb 中的自动加载器路径中删除路径,例如 .然而,这感觉就像一个猴子补丁,用于硬塞旧方式或将对象组织到新的轨道方式中。config.autoload_paths -= Dir["#{config.root}/app/services/demo/"]

命名对象的正确方法是什么,或者是 rails 6 组织它的正确方法,而不仅仅是强迫 rails 采用旧方式?

Ruby-on-rails 命名空间 Ruby-on-rails-6 Zeitwerk

评论


答:

8赞 rmlockerd 8/5/2020 #1

说Zeitwerk消除了“命名空间的需要”是不正确的。Zeitwerk 确实会自动加载 (除了 、 和 )。下面的任何目录都将加载到“root”命名空间中。但是,Zeitwerk 还为这些根目录下的任何目录“自动激活”模块。所以:appassetsjavascriptsviewsapp

/models/foo.rb => Foo
/services/bar.rb => Bar
/services/registration/add_demo_data.rb => Registration::AddDemoData

如果您已经习惯于从“非标准”目录加载常量(通过添加 ),通常不会有太大变化。不过,有几种情况确实需要一些调整。第一种是迁移仅将自身添加到自动加载路径的项目的位置。在经典(Rails 6 之前)中,这允许你使用 to contain ,而在 Zeitwerk 中,它希望它只包含 。这就是您上面提到的情况,建议从自动加载路径中排除该目录。另一种选择是简单地添加一个包装器目录,例如 .config.autoload_pathsappapp/api/base.rbAPI::BaseBaseapp/api/api/base.rb

第二个需要注意的问题是Zeitwerk如何从文件名推断常量。从 Rails 迁移指南中:

classic模式从缺少的常量名称推断文件名 (下划线),而 Zeitwerk 模式从文件中推断常量名称 名称(骆驼)。这些帮助者并不总是彼此相反, 特别是如果涉及首字母缩略词。例如,是 ,但 是 ,不是 。"FOO".underscore"foo""foo".camelize"Foo""FOO"

所以,实际上等同于Zeitwerk,而不是./api/api/base.rbApi::BaseAPI::Base

Zeitwerk 包含一个 rake 任务,用于验证项目中的自动加载:

% bin/rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/api/base.rb to define constant Base

评论

0赞 ilvez 3/13/2021
谢谢你提出和回答这个问题。rmlockerd,你不仅让我度过了一天,而且让我度过了整整一个月!:man-bowing:
0赞 Solomons_Ecclesiastes 5/7/2022
我认为应该是bin/rails Zeitwerk:checkbin/rails zeitwerk:check
0赞 ilvez 3/13/2021 #2

编辑:

正如评论中所澄清的,您实际上不需要向 .这是 Zeitwerk 在 Rails 中的默认行为,当您将代码放在 的某个子目录下时。autoload_pathsapp

原文答案:

我发布了单独的答案,但实际上接受的答案包含所有好信息。由于我的评论比允许的要大,我选择为那些正在为类似问题而苦苦挣扎的人添加单独的答案。

我们在应用程序下创建了“组件”,其中我们分隔了特定于域的命名空间/包。它们与一些“非组件”Rails 部件共存,这些部件很难在组件下移动。使用经典的自动加载器,我们添加了autoload_paths。#{config.root}/app

Zeitwerk 的此设置失败,从autoload_paths中删除也无济于事。rmlockerd 建议移动到 下 感动了我的想法,在此目录下创建单独的和移动所有组件,并将此路径添加到 .Zeitwerk 已点赞。"#{config.root}/app"app/api//app/api/api'app/components'autoload_paths

评论

1赞 Xavier Noria 7/14/2022
您不需要手动添加到自动加载路径中,它是由 Rails 自动完成的。app/components
0赞 ilvez 7/19/2022
谢谢你让我知道。我有点惊讶它真的有效。我试图找到一些关于它的文档,但无法找到。
1赞 Xavier Noria 7/19/2022
当然,它记录在这里 guides.rubyonrails.org/......
0赞 ilvez 7/19/2022
哦。我以为这有什么神奇的东西,但它实际上适用于任何名称,除了少数例外。谢谢你的澄清。app/components