英文:
Looking for a more granular way to organize Rails models and understanding autoloader
问题
在一个测试应用程序(Rails 7.0,Ruby 3.2)中,我目前正在尝试一种不同于默认结构的模型布局。我的想法是创建以下类似的结构,其中每个模型的所有相关代码都放在一起,而每个组都使用相同的支持文件命名:
app/
└── models/
├── accounts
│ ├── account.rb
│ └── support
│ └── greetable.rb
└── users
├── user.rb
└── support
└── greetable.rb
这是我想象中此设置的文件结构:
# models/accounts/account.rb
class Account < ApplicationRecord
include Greetable
end
# models/accounts/support/greetable.rb
module Greetable
def greet
"Hello, Account!"
end
end
# models/users/user.rb
class User < ApplicationRecord
include Greetable
end
# models/users/support/greetable.rb
module Greetable
def greet
"Hello, User!"
end
end
有关如何为这种设置定义自动加载配置的任何想法?我希望 Zeitwerk 能够自动找到最近的 Greetable 模块,但事实并非如此。
欢迎提供有关这个主题的任何想法、思考或讨论。
英文:
In a test application (Rails 7.0, Ruby 3.2) I'm currently experimenting with a different structure of models than the default. The idea was to have something like this where all related code of a model lives together and each group uses same naming for support files:
app/
└── models/
├── accounts
│ ├── account.rb
│ └── support
│ └── greetable.rb
└── users
├── user.rb
└── support
└── greetable.rb
This was how I imagined the files of this setup:
# models/accounts/account.rb
class Account < ApplicationRecord
include Greetable
end
# models/accounts/support/greetable.rb
module Greetable
def greet
"Hello, Account!"
end
end
# models/users/user.rb
class User < ApplicationRecord
include Greetable
end
# models/users/support/greetable.rb
module Greetable
def greet
"Hello, User!"
end
end
Any idea how to define autoload config for this setup? Was hoping Zeitwerk does its magic and automatically finds the closest Greetable module but this is not how it works.
All thoughts/ideas/discussions around this topic are much appreciated.
答案1
得分: 1
Zeitwerk的基本假设是根目录(在Rails中称为“autoload路径”)表示一个具体的、固定的命名空间。默认情况下,该命名空间是顶级命名空间Object
。
然后,子目录中的命名是相对于根目录的。因此,使用默认设置,app/models
对应于Object
,而app/models/accounts
对应于Accounts
。
Zeitwerk还有一个用于将文件组合在一起以进行组织的功能,而不引入命名空间。这就是折叠(collapsing)。
通过折叠,您可以实现所需的目标(未经测试):
# config/initializers/autoloading.rb
main = Rails.autoloaders.main
main.collapse("#{Rails.root}/app/models/*")
main.collapse("#{Rails.root}/app/models/*/support")
正如文档中所述,这些通配符模式会在启动时和每次重新加载时进行评估,因此如果您在app/models
下添加新的组,它们将被捕获。
英文:
The basic premise in Zeitwerk is that a root directory (called "autoload path" in Rails) represents a concrete, fixed namespace. By default, that namespace is the top-level one, Object
.
The expected naming in that subtree is then relative to that root. So, with default settings, app/models
is Object
, and app/models/accounts
is Accounts
.
There is a feature of Zeitwerk thought for grouping files together for organizational purposes, without introducing namespaces. That is collapsing.
What you want to accomplish is possible via collapsing (untested):
# config/initializers/autoloading.rb
main = Rails.autoloaders.main
main.collapse("#{Rails.root}/app/models/*")
main.collapse("#{Rails.root}/app/models/*/support")
As the documentation says, those glob patterns are evaluated on boot and again on each reload, so if you add new groups under app/models
they will be picked up.
答案2
得分: 1
以下是翻译好的部分:
你不需要配置任何东西。你只需要理解它的预期工作方式。
Zeitwerk的工作方式是,它期望自动加载根目录的任何子目录中的文件(任何子文件夹或app
和/app/models/concerns
,/app/controllers/concerns
)嵌套在一个与文件夹名称相符的模块(或类)中:
# models/users/support/greetable.rb
module Users
module Support
module Greetable
def greet
"Hello, User!"
end
end
end
end
Zeitwerk会自动创建隐式命名空间(模块),因此即使你的文件只包含:
module Greetable
def greet
"Hello, User!"
end
Zeitwerk会将常量嵌套为Users::Support::Greetable
。我猜这个"没有工作",因为你期望Greetable
在顶层命名空间中。
这不仅是Zeitwerk的期望,也是其他开发人员的期望,也是组织代码的一种被接受的方法,因为模块嵌套提供的封装可以防止冲突。这实际上并不是Zeitwerk的独有特性 - 它实际上只是对在它发明之前就已经使用的约定进行了编码。
在Rails中将一切都放入全局命名空间是出于方便而忽略了在我们的脑海中警告的铃声。这远非组织代码的理想方式。
虽然你可以通过使用折叠或添加额外的自动加载根目录将这个嵌套文件常量变成顶层常量,但从组织的角度来看,我真的会质疑为什么你想要违反最小惊讶原则,因为这并没有提供真正的好处。
英文:
You don't need to configure anything. You just have to understand how its intented to work.
The way that Zeitwerk works is that it expects any files in subdirectories of an autoloading root (any subfolder or app and /app/models/concerns
, /app/controllers/concerns
) to be nested in a module (or class) with the a name according to the folder:
# models/users/support/greetable.rb
module Users
module Support
module Greetable
def greet
"Hello, User!"
end
end
end
end
Zeitwerk will automatically create the implicit namespaces (the modules) so even if your file just contains:
module Greetable
def greet
"Hello, User!"
end
end
Zeitwerk will nest the constant as Users::Support::Greetable
. I'm guessing this "didn't work" because you where expecting Greetable
to be in the top level namespace.
This is not just what Zeitwerk expects but also what other developers expect and is the accepted method of organizing your code as the encapsulation provided by module nesting prevents conflicts. This isn't actually something unique to Zeitwerk either - it really just codifies conventions that were used long before its invention.
Plopping everything into the global namespace in Rails is something we do out of convenience while ignorning the warning bells that are going off in the back of our heads. Its far from an ideal way to organize code.
While you could make this nested file constant a top level constant by using collapsing or adding additional autoloading roots from an organizational standpoint I would really question why you would want violate the principle of least suprise like this as it provides no real benefits.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论