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

pho*_*ley 4 namespaces ruby-on-rails ruby-on-rails-6 zeitwerk

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 采用旧方法?

rml*_*erd 8

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

/models/foo.rb => Foo
/services/bar.rb => Bar
/services/registration/add_demo_data.rb => Registration::AddDemoData
Run Code Online (Sandbox Code Playgroud)

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

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

classicmode 从缺失的常量名称(下划线)推断文件名,而 zeitwerk 模式从文件名推断常量名称(驼峰化)。这些助手并不总是彼此相反,特别是在涉及缩写词时。例如,"FOO".underscore 是"foo",但是"foo".camelize是"Foo",不是"FOO"。

所以,/api/api/base.rb实际上等于Api::BaseZeitwerk 中的 ,而不是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
Run Code Online (Sandbox Code Playgroud)