Rails 3.1 Engines – Mountable or Full? – Part 2
This is a continuation of my post Rails 3.1 Engines – Mountable or Full? – Part 1 which highlights some key features of a full engine. There are a few differences with a mountable engine, which I will try to explain below.
Since the engine has an isolated namespace, the controllers, models, helpers, etc. are all bundled within the module MyEngine. The folder structure reflects this change as well. Unlike a full engine, the mountable engine generated the typical assets that are normally associated with a Rails application, namely application.js, application.css, and application_controller.rb. Note the namespacing that occurs with the application controller:
This namespacing occurs throughout the engine. There are subtle differences in terms of routing as well. With a mountable engine, the engine’s route file looks like this:
Remember, the full application’s routes start with
Notice that the engine is bundled under a “mounted” route at /my_engine. This means that all of the engine’s functionality will be located under the engine’s root location of /my_engine within the dummy (parent) application. This type of structure suggests that a mountable engine is best suited for situations when a developer wants the engine to behave as its own application operating in parallel with the parent application. Most of the engine’s features are isolated and independent of the parent application. To test this, I plugged the mountable engine into a test application.
With a full engine, the Post routes were included directly into the parent application automatically. This time, using the mountable engine and running
…and that’s it. All of the engine’s functionality is bundled into the parent application underneath the route /my_engine.
After running
Of course, navigating to /my_engine/posts in the parent application showed an almost blank page with the words “Hi There!” The controller actions and views from the engine were incorporated into the parent application, but only under the namespaced route /my_engine.
Next, I tested if the view could be overridden by the parent application. I created a new view template for the Post index action within the parent application that looked like this:
No change and of course not! Our engine is namespaced, remember? Instead, I moved the template to
I then changed the parent application’s view template to use the helper.
Visiting the page resulted in the words “Good Bye!” and “hello world.” Therefore, the engine’s helper methods were directly exposed to the parent application, allowing the parent to use them.
To be honest, I did not expect this to work. There is a section in the Rails comments about sharing an isolated engine’s helpers with the parent application by using
Mountable Engines
Engine Details
To generate a mountable engine, runrails plugin new myengine --mountable
on the console. This will generate the engine’s basic structure.
The first difference I noted with a mountable engine is that, by default, the engine is namespaced. The engine’s starting config is:
1
2
3
4
5
6
7 |
module MyEngine # my_engine/lib/my_engine/engine.rb class Engine < Rails::Engine isolate_namespace MyEngine end end |
1
2
3
4
5
6 |
# my_engine/app/controllers/my_engine/application_controller.rb module MyEngine class ApplicationController < ActionController::Base end end |
1
2
3
4
5 |
# my_engine/config/routes.rb MyEngine::Engine.routes.draw do # route stuff goes here end |
Rails.application.routes.draw
. The dummy application’s routes file was generated as:
1
2
3
4
5
6 |
# my_engine/test/dummy/config/routes.rb Rails.application.routes.draw do mount MyEngine::Engine => "/my_engine" end |
Routing
I used a model within the engine called “Post”, along with the migration, controller, and helper to match (rails generate scaffold post title:string body:text
). Now my engine’s routes file looks like this:
1
2
3
4
5 |
# my_engine/config/routes.rb MyEngine::Engine.routes.draw do resources :posts end |
rake routes
within the parent application, nothing happened! That’s because I did not mount the engine in a similar manner to what the dummy application in our engine is using. The parent application’s route file had to be adjusted like so:
1
2
3
4
5 |
# parent_app/config/routes.rb ParentApp::Application.routes.draw do mount MyEngine::Engine => "/my_engine" end |
rake routes
now produced the following:
1 |
my_engine /my_engine { :to =>MyEngine::Engine} |
rake my_engine:install:migrations
and rake db:migrate
in the parent application, I was ready to test how the engine’s controllers and helpers would integrate. [note: running migrations from an isolated engine will prefix your database tables with the engine name]. As with the full engine, I established a simple view template:
1
2
3 |
# my_engine/app/views/my_engine/posts/index.html.erb <p>Hi There!</p> |
1
2
3 |
# parent_app/app/views/posts/index.html.erb <p>Good Bye!</p> |
parent_app/app/views/my_engine/posts/index.html.erb
. Now visiting /my_engine/posts in the parent application showed “Good Bye!” That’s better. Next I decided to test out the engine’s helpers. I added a method to the Post helper inside the engine.
1
2
3
4
5
6
7
8
9 |
# my_engine/app/helpers/my_engine/posts_helper.rb module MyEngine module PostsHelper def test raw( "<p>hello world</p>" ) end end end |
1
2
3
4 |
# parent_app/app/views/posts/index.html.erb <p>Good Bye!</p> <%= test %> |
helper MyEngine::Engine.helpers
in the application controller of the parent. It states that in doing so “[the parent application] will include all of the helpers from engine’s directory.” Perhaps this is a bug in the release candidates of Rails 3.1? Maybe my interpretation of the instructions is way off? I’m not sure. If this does change by the time you use it, and you can’t access the engine’s helpers, I suggest trying the method described.
Summary
It seems as though mountable engines are best suited as complete applications in themselves which run along side a parent application. The engine routes, controllers, models, helpers, etc. are namespaced and bundled into the mounted route within the parent application, thus avoiding namespace conflicts. If I’m way off in my assessment or there are other things which I have missed, drop me a line and I’ll add them!