Rails 6 中的命名空间服务对象与 Zeitwerk 自动加载器

Namespacing service objects in Rails 6 with Zeitwerk autoloader

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

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

一个解决方案 suggested by the rails guide 是从 application.rb 中的自动加载器路径中删除路径,例如config.autoload_paths -= Dir["#{config.root}/app/services/demo/"]。然而,这感觉就像一个猴子补丁,用于用旧方法拔鞋或将对象组织成新的 rails 方式。

命名空间对象的正确方法是什么,或者组织它的 rails 6 方法是什么而不只是强迫 rails 进入旧方法?

说 Zeitwerk 消除了 'the need for namespacing' 是不正确的。 Zeitwerk 确实会自动加载 app 的所有子目录(assetsjavascriptsviews 除外)。 app 下的所有目录都加载到 'root' 命名空间中。但是,Zeitwerk 还 'autovivifies' 模块用于这些根目录下的任何目录。所以:

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

如果您已经习惯从 'non-standard' 目录加载常量(通过添加到 config.autoload_paths),通常不会有太大变化。不过,有几个案例确实需要进行一些调整。第一个是您要迁移的项目,该项目仅将 app 自身添加到自动加载路径。在经典 (pre-Rails 6) 中,这允许您使用 app/api/base.rb 来包含 API::Base,而在 Zeitwerk 中它期望它只包含 Base。这就是您在上面提到的情况,建议将该目录从自动加载路径中排除。另一种选择是简单地添加一个包装目录,如 app/api/api/base.rb.

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

classic mode infers file names from missing constant names (underscore), whereas zeitwerk mode infers constant names from file names (camelize). These helpers are not always inverse of each other, in particular if acronyms are involved. For instance, "FOO".underscore is "foo", but "foo".camelize is "Foo", not "FOO".

因此,/api/api/base.rb 实际上等同于 Zeitwerk 中的 Api::Base,而不是 API::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

我发布了单独的答案,但实际上接受的答案包含了所有有用的信息。由于我的评论超出了允许的范围,我选择为那些遇到类似问题的人添加单独的答案。

我们在应用程序下创建了“组件”,我们将特定领域分开 namespaces/packages。它们与一些“非组件”Rails 部件共存,这些部件很难在组件下移动。对于经典自动加载器,我们在 autoload_paths.

中添加了 #{config.root}/app

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