Rails 3.1: Engine vs. Mountable App

30,535

Solution 1

I have noticed the following:

Full Engine

With a full engine, the parent application inherits the routes from the engine. It is not necessary to specify anything in parent_app/config/routes.rb. Specifying the gem in Gemfile is enough for the parent app to inherit the models, routes etc. The engine routes are specified as:

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

No namespacing of models, controllers, etc. These are immediately accessible to the parent application.

Mountable Engine

The engine's namespace is isolated by default:

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

With a mountable engine, the routes are namespaced and the parent app can bundle this functionality under a single route:

# 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 

Models, controllers, etc are isolated from the parent application - although helpers can be shared easily.

These are the main differences I have spotted. Perhaps there are others? I have asked over here, but have yet to receive a response.

My impression is that since a full engine does not isolate itself from the parent application, it is best used as a standalone application adjacent to the parent app. I believe name clashes could occur.

A mountable engine could be used in situations where you want to avoid name conflicts and bundle the engine under one specific route in the parent application. For example, I am working on building my first engine designed for customer service. The parent application could bundle it's functionality under a single route such as:

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

If I'm way off in my assumptions, someone please let me know and I'll fix this response. I have made a small article about the subject here Cheers!

Solution 2

Both options will generate an engine. The difference is that --mountable will create the engine in an isolated namespace, whereas --full will create an engine that shares the namespace of the main app.

The differences will be manifested in 3 ways:

1) The engine class file will call 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) The engine's config/routes.rb file will be namespaced:

Full engine:

Rails.application.routes.draw do
end

Mounted engine:

MyMountableEngine::Engine.routes.draw do
end

3) The file structure for controllers, helpers, views, and assets will be namespaced:

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


Explanation

The use case for the --full option seems to be very limited. Personally I can't think of any good reason why you'd want to separate your code into an engine without isolating the namespace as well- It would essentially just give you two tightly coupled applications sharing identical file structures and all the conflicts and code leakage that entails.

Every piece of documentation I've seen demonstrates the --mountable option, and indeed the current edge guide strongly encourages you to include isolate namespace- which is the same as saying use --mountable over --full.

Finally there's terminology confusion: Unfortunately rails plugin -h shows the following descriptions:

[--full] # Generate a rails engine with bundled Rails application for testing
[--mountable] # Generate mountable isolated application

This gives the impression that you use --full to create an "engine" and --mountable to create something else called a "mountable application", when in fact they're both engines - one namespaced and one not. That's bound to lead to confusion as users looking to create an engine will likely assume that --full is the more relevant option.

Conclusion

  • rails plugin new something --full = Engine in your app's namespace. (Why would you?)
  • rails plugin new something --mountable = Engine with it's own namespace. (Awesome)

References

Solution 3

i was wondering the same and, hence, ended up here. it seems to me that the earlier answers basically cover the question, but i thought the following might help as well:

# 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

of particular interest (to me) is the fact that there is no difference between

rails plugin new test-plugin -T --mountable

and

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

Solution 4

My understanding of the difference is that engines are like plugins, and add functionality to existing applications. While mountable apps are essentially an application, and can stand alone.

So if you want to be able to run it by itself or within another application you would make a mountable app. If you intend for it to be an addition to existing applications, but not run by itself you would make it an engine.

Solution 5

The difference, I believe, is that a mountable app's are isolated from the host app, so they can't share classes - models, helper etc. This is because a Mountable app is a Rack endpoint (i.e a Rack app in its own right).

Disclaimer: I have, like most, only just started toying with Rails 3.1.

Share:
30,535
Jeremy Raines
Author by

Jeremy Raines

Updated on March 31, 2020

Comments

  • Jeremy Raines
    Jeremy Raines about 4 years

    Can someone help me understand the differences between a Rails Engine and a Mountable app? In Rails 3.1, you can create either one with the "rails new plugin ___" command.

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

    When would you want to use one versus the other? I know you can package an Engine as a gem, for one. Is that not the case for Mountable Apps? What other differences are there?

  • Jeremy Raines
    Jeremy Raines almost 13 years
    Agreed. One thing that seems strange though is that by default, an Engine gives you a "models" folder, but a Mountable App does not. I wonder if the "best practice" would be to have generators that create models for the including app, since it seems like you wouldn't want to have any migrations in the engine/moutable
  • Slick23
    Slick23 over 12 years
    Can a mountable engine ever be routed/mounted at the root of the parent app?
  • Benoit Garret
    Benoit Garret over 12 years
    @JustinM you could try mount MyEngine::Engine => "/". It works for resources, maybe that's also the case for engines.
  • Theo Scholiadis
    Theo Scholiadis almost 12 years
    @astjohn Great sum-up of your blogs. But wouldn't it be the other way round? Would a Full Engine be "incomplete" and need the parent app to work, whereas the Mountable Engine can work stand-alone, since it is "isolated" from the parent app?
  • nathanvda
    nathanvda almost 11 years
    There is one good reason to use --full: if you have parts of a rails website you want to keep integrated (not in an isolated namespace) and still share among different rails projects. Also it can be simpler than that: maybe your gem does not add that much, but you want to be able to hook it in correctly.
  • Yarin
    Yarin over 10 years
    @nathanvda - Right, but I think if you're sharing something across multiple projects it really should be namespaced, because you're basically using it as a plugin
  • Jwan622
    Jwan622 almost 8 years
    I think you may want to use --full if you want to isolate your files, namespace your call sites for when you do Admin::AdminService.some_action but not have to change your routes if other client side applications like an Ember app use the routes related to the code you want to isolate. --full seems like an intermediate step that may be easier to implement.
  • Mankalas
    Mankalas over 5 years
    I'm currently working on an international application that needs to handle country-specific regulations, but yet exposes the same interface to the world. I've got one instance of "Core" per country, no need to handle all at once. The "country engines" don't make sense on their own, so the coupling with the "core" app isn't an issue. However, I don't want them to be in their own namespace because the core app must not know in which country it operates. I feel a "full" engine is more like organising your files and classes in a modular way, but still keeping your "monolith" in place.
  • Mankalas
    Mankalas over 5 years
    Maybe that's because --full has precedence over --mountable?