为什么Rails引擎的gem名称必须与模块/命名空间名称”匹配”?

Why does a Rails engine's gem name have to “match” the module/namespace name?

我正在制造我的第一个Rails引擎。我称它为my_engine,因此它生成文件

1
2
3
lib/my_engine.rb
lib/my_engine/engine.rb
lib/my_engine/version.rb

全部都有名为MyEngine的模块。并且在gemspec中,gem名称也设置为my_engine

如果我创建模型my_model,它将进入app/models/my_engine/my_model.rb,并生成为

1
2
3
4
module MyEngine
  class MyModel < ActiveRecord::Base
  end
end

如果我在此处创建一个类方法,并将gem放入Rails项目中,则一切正常。

1
2
3
4
5
6
7
def self.hello
 "Hello from your Engine's model!"
end

$ bundle exec rails c
[1] (pry) main: 0> MyEngine::MyModel.hello
=>"Hello from your Engine's model!"

但是,我不希望gem名称为my_engine。但是,如果我将gemspec中的名称更改为其他名称,例如what-i-really-want-to-name-it,则一切都会停止工作。尽管Rails可以看到我的名称空间,但它无法看到我的模型。我的确是在Rails应用程序中更改了gem的名称并重新打包,因此这不是问题。

1
2
3
4
5
6
$ bundle exec rails c
[1] (pry) main: 0> MyEngine::VERSION
=>"0.0.1"   # default version from engine generation
[2] (pry) main: 0> MyEngine::MyModel.hello
NameError: uninitialized constant MyEngine::MyModel
from (pry):2:in `__pry__'

为什么" this"直接与ruby名称绑定?有什么解决方法吗?我真的很想让ruby名称和模块名称具有不同的值。

使用:Rails 4.2.6,Ruby 2.3.0


答案是Rails的基本概念之一是:

Convention over configuration.

当您决定使用配置替代约定的原则时,这就是Rails的反模式。是否有可能做到这一点并保持快乐并拥有可正常运行的Rails应用程序?可以,但这不是您要在Rails应用程序上进行工作的示例所示的代码。

因此,惯例是模块名称与gem名称匹配。这只是一个约定,但是由于约定是Rails镇达成的共同商定的法律,因此如果您不遵循约定,就会成为反模式。

为回应OP评论而添加

Rails引擎的典型特征是它们使用隔离的名称空间和隔离的资源。ruby没有,因此实际上答案是肯定的,使用Rails引擎会强制执行ruby不存在的命名空间约定。中间件使用该命名空间在运行时将main_app与引擎分离。两个例子来说明:

一个极端的例子:您可以将一个应用程序作为引擎安装在其自身上。命名空间将彼此隔离,因此中间件服务在正确的进程上起作用,这些进程仅由名称空间来区分。

另一个示例:main_app上安装了2个引擎。现在基本上有3个应用程序正在运行。在这种情况下,您将如何实现非常规名称空间隔离?

所以...
是否可以在Rails引擎中加入不一致的名称空间?大概。我从来没有试过。但是您的引擎将无法移植。更糟糕的是,安装它的人将无法安装其他符合标准的引擎(并共享main_app和中间件堆栈),因为您已将它们置于破坏常规Rails引擎的配置迷宫中。最后一部分是理论。