Rails 3.1: 引擎 vs. 可挂载应用程序

121

有人能帮我理解Rails Engine和Mountable应用程序之间的区别吗?在Rails 3.1中,您可以使用"rails new plugin _ "命令创建任何一个。

rails plugin new forum --full        # Engine
rails plugin new forum --mountable   # Mountable App

你何时会选择使用其中一个而不是另一个?我知道你可以将Engine打包成gem。那对于Mountable应用程序来说是否也是如此呢?还有其他区别吗?

5个回答

145

我注意到以下内容:

完整引擎

使用完整引擎时,父应用程序继承了引擎的路由。在parent_app/config/routes.rb中不需要指定任何内容。在Gemfile中指定gem就足够了,这样父应用程序就可以继承模型、路由等。 引擎路由的指定方式如下:

# my_engine/config/routes.rb 
Rails.application.routes.draw do 
  # whatever 
end 

模型、控制器等没有命名空间,它们可以直接被父应用程序访问。

可挂载引擎

引擎的命名空间默认是隔离的:

# my_engine/lib/my_engine/engine.rb
module MyEngine 
  class Engine < Rails::Engine 
    isolate_namespace MyEngine 
  end 
end

通过可挂载引擎,路由被命名空间化,父级应用可以将该功能捆绑在单个路由下:

# my_engine/config/routes.rb 
MyEngine::Engine.routes.draw do 
  #whatever 
end 

# parent_app/config/routes.rb 
ParentApp::Application.routes.draw do 
    mount MyEngine::Engine => "/engine", :as => "namespaced" 
end 

模型、控制器等与父应用程序隔离 - 尽管帮助程序可以轻松共享。

这些是我发现的主要区别。也许还有其他区别? 我在这里提出了问题,但尚未收到答复。

我的印象是,由于完整引擎不会与父应用程序隔离,因此最好将其用作毗邻父应用程序的独立应用程序。我相信可能会发生名称冲突。

可挂载引擎可用于避免名称冲突并将引擎捆绑到父应用程序中的一个特定路由下。例如,我正在构建我的第一个面向客户服务的引擎。父应用程序可以将其功能捆绑到单个路由下:

mount Cornerstone::Engine => "/cornerstone", :as => "help" 

如果我的假设完全错误,请让我知道,我会修正这个回答。我写了一篇关于这个主题的小文章here


1
可否将可挂载引擎路由/挂载在父应用程序的根目录下? - Slick23
3
你可以尝试使用mount MyEngine::Engine => "/"。这对于资源是有效的,也许对于引擎也是如此。 - Benoit Garret
2
@astjohn,你的博客总结得非常好。但是反过来不是更合适吗?全引擎会是“不完整”的,需要父应用程序才能工作,而可安装引擎可以独立工作,因为它与父应用程序“隔离”。 - Theo Scholiadis

43

两个选项都会生成一个engine,不同之处在于--mountable将在隔离的命名空间中创建引擎,而--full将创建共享主应用程序命名空间的引擎。

这些差异将通过3种方式体现:

1)引擎类文件将调用isolate_namespace

lib/my_full_engine/engine.rb:

module MyFullEngine
  class Engine < Rails::Engine
  end
end

lib/my_mountable_engine/engine.rb:

module MyMountableEngine
  class Engine < Rails::Engine
    isolate_namespace MyMountableEngine # --mountable option inserted this line
  end
end

2) 引擎的config/routes.rb文件将被命名空间化:

完整引擎:

Rails.application.routes.draw do
end

安装的引擎:

MyMountableEngine::Engine.routes.draw do
end

3)控制器、帮助程序、视图和资产的文件结构将被命名空间化:

创建 app/controllers/my_mountable_engine/application_controller.rb
创建 app/helpers/my_mountable_engine/application_helper.rb
创建 app/mailers create app/models
创建 app/views/layouts/my_mountable_engine/application.html.erb
创建 app/assets/images/my_mountable_engine
创建 app/assets/stylesheets/my_mountable_engine/application.css
创建 app/assets/javascripts/my_mountable_engine/application.js
创建 config/routes.rb 创建 lib/my_mountable_engine.rb
创建 lib/tasks/my_mountable_engine.rake
创建 lib/my_mountable_engine/version.rb
创建 lib/my_mountable_engine/engine.rb


说明

--full 选项的使用场景似乎非常有限。个人来看,我想不出任何好的理由为什么要把代码分离成引擎而不隔离命名空间- 实际上这只是给你两个紧密耦合的应用程序,共享相同的文件结构和所有冲突和代码泄漏。

我见过的每一篇文档都演示了 --mountable 选项,实际上当前的 edge guide 强烈建议您包括隔离命名空间——这意味着使用 --mountable 而不是 --full

最后,存在术语混淆:不幸的是 rails plugin -h 显示以下描述:

[--full] # 生成带有捆绑Rails应用程序进行测试的 Rails引擎
[--mountable] # 生成可装载的隔离应用程序

这给人留下了这样的印象:您使用 --full 创建一个“引擎”,使用 --mountable 创建另一种称为“可挂载应用程序”的东西,而事实上它们都是引擎-一个已命名空间化,一个没有。这必定会导致用户困惑,因为寻找创建引擎的用户很可能会认为 --full 是更相关的选项。

结论

  • rails plugin new something --full = 将引擎嵌入到您的应用程序命名空间中。(为什么要这样做?)
  • rails plugin new something --mountable = 具有自己命名空间的引擎。(非常棒)

参考资料


9
使用 --full 的一个好处是:如果你想保留 Rails 网站的某些部分,而不是将其隔离在命名空间中,并且仍然可以在不同的 Rails 项目之间共享。它也可以更简单一些:也许你的 gem 并没有添加太多内容,但你希望能够正确地将其挂钩。 - nathanvda
3
@nathanvda - 是的,但我认为如果你要在多个项目中共享某个东西,它真的 应该 有命名空间,因为你实际上是将其用作插件。 - Yarin
我认为如果你想隔离你的文件,并在调用站点上使用命名空间,以便在不更改路由的情况下使用 Admin::AdminService.some_action,但其他客户端应用程序(如Ember应用程序)使用与您要隔离的代码相关的路由,则可能需要使用 --full。--full似乎是一个较容易实现的中间步骤。 - Jwan622
我正在开发一个国际应用程序,需要处理特定国家的法规,但同时向全球公开相同的接口。每个国家都有一个“核心”实例,不需要同时处理所有国家。这些“国家引擎”本身没有意义,因此与“核心”应用程序的耦合不是问题。然而,我不希望它们位于自己的命名空间中,因为核心应用程序不能知道它所在的国家。我认为,“完整”的引擎更像是以模块化方式组织文件和类,但仍然保持“单体”的位置。 - Mankalas

17

我也曾经想过同样的问题,所以来到这里。看起来之前的回答已经基本覆盖了这个问题,但是我认为以下的内容可能也会有所帮助:

# generate plugins (NOTE: using same name each time to minimize differences)
# -----------------------------------------------------------------------------

$ rails plugin new test-plugin -T
$ mv test-plugin{,.01}

$ rails plugin new test-plugin -T --mountable
$ mv test-plugin{,.02}

$ rails plugin new test-plugin -T --full
$ mv test-plugin{,.03}

$ rails plugin new test-plugin -T --full --mountable
$ mv test-plugin{,.04}




# compare "stock" (01) with "mountable" (02)
# -----------------------------------------------------------------------------

$ diff -r test-plugin.01 test-plugin.02

Only in test-plugin.02: app
Only in test-plugin.02: config
Only in test-plugin.02/lib/test-plugin: engine.rb
diff -r test-plugin.01/lib/test-plugin.rb test-plugin.02/lib/test-plugin.rb
0a1,2
> require "test-plugin/engine"
> 
Only in test-plugin.02: script
diff -r test-plugin.01/test-plugin.gemspec test-plugin.02/test-plugin.gemspec
18a19
>   # s.add_dependency "jquery-rails"




# compare "stock" (01) with "full" (03)
# -----------------------------------------------------------------------------

$ diff -r test-plugin.01 test-plugin.03
Only in test-plugin.03: app
Only in test-plugin.03: config
Only in test-plugin.03/lib/test-plugin: engine.rb
diff -r test-plugin.01/lib/test-plugin.rb test-plugin.03/lib/test-plugin.rb
0a1,2
> require "test-plugin/engine"
> 
Only in test-plugin.03: script
diff -r test-plugin.01/test-plugin.gemspec test-plugin.03/test-plugin.gemspec
18a19
>   # s.add_dependency "jquery-rails"




# compare "mountable" (02) with "full" (03)
# -----------------------------------------------------------------------------

$ diff -r test-plugin.02 test-plugin.03

Only in test-plugin.03/app/assets/javascripts/test-plugin: .gitkeep
Only in test-plugin.02/app/assets/javascripts/test-plugin: application.js
Only in test-plugin.03/app/assets/stylesheets/test-plugin: .gitkeep
Only in test-plugin.02/app/assets/stylesheets/test-plugin: application.css
Only in test-plugin.03/app/controllers: .gitkeep
Only in test-plugin.02/app/controllers: test-plugin
Only in test-plugin.03/app/helpers: .gitkeep
Only in test-plugin.02/app/helpers: test-plugin
Only in test-plugin.03/app/mailers: .gitkeep
Only in test-plugin.03/app/models: .gitkeep
Only in test-plugin.03/app/views: .gitkeep
Only in test-plugin.02/app/views: layouts
diff -r test-plugin.02/config/routes.rb test-plugin.03/config/routes.rb
1c1
< TestPlugin::Engine.routes.draw do
---
> Rails.application.routes.draw do
diff -r test-plugin.02/lib/test-plugin/engine.rb test-plugin.03/lib/test-plugin/engine.rb
3d2
<     isolate_namespace TestPlugin




# compare "mountable" (02) with "full & mountable" (04)
# -----------------------------------------------------------------------------

$ diff -r test-plugin.02 test-plugin.04

<no difference>




# compare "full" (03) with "full & mountable" (04)
# -----------------------------------------------------------------------------

$ diff -r test-plugin.03 test-plugin.04

Only in test-plugin.03/app/assets/javascripts/test-plugin: .gitkeep
Only in test-plugin.04/app/assets/javascripts/test-plugin: application.js
Only in test-plugin.03/app/assets/stylesheets/test-plugin: .gitkeep
Only in test-plugin.04/app/assets/stylesheets/test-plugin: application.css
Only in test-plugin.03/app/controllers: .gitkeep
Only in test-plugin.04/app/controllers: test-plugin
Only in test-plugin.03/app/helpers: .gitkeep
Only in test-plugin.04/app/helpers: test-plugin
Only in test-plugin.03/app/mailers: .gitkeep
Only in test-plugin.03/app/models: .gitkeep
Only in test-plugin.03/app/views: .gitkeep
Only in test-plugin.04/app/views: layouts
diff -r test-plugin.03/config/routes.rb test-plugin.04/config/routes.rb
1c1
< Rails.application.routes.draw do
---
> TestPlugin::Engine.routes.draw do
diff -r test-plugin.03/lib/test-plugin/engine.rb test-plugin.04/lib/test-plugin/engine.rb
2a3
>     isolate_namespace TestPlugin

我特别关注的是,事实上没有区别。
rails plugin new test-plugin -T --mountable

并且

rails plugin new test-plugin -T --full --mountable

也许这是因为 --full 优先于 --mountable - Mankalas

9

我的理解是:引擎就像插件一样,可以为现有应用程序添加功能。而可挂载的应用程序本质上是一个应用程序,可以独立运行。

因此,如果您希望能够单独运行或在其他应用程序内部运行,则应制作可挂载的应用程序。如果您打算将其作为现有应用程序的附加功能,但不是独立运行,则应将其制作为引擎。


2
我认为不同之处在于可挂载应用与宿主应用隔离,因此它们不能共享类-模型、帮助器等。这是因为可挂载应用是一个Rack端点(即自己的Rack应用)。
免责声明:像大多数人一样,我只是开始玩Rails 3.1。

同意。不过有一件事情似乎很奇怪,就是默认情况下,引擎会给你一个“models”文件夹,但可挂载应用程序则没有。我想知道“最佳实践”是否应该有生成器为包含的应用程序创建模型,因为似乎你不希望在引擎/可挂载中有任何迁移。 - Jeremy Raines

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接