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.

Mountable Engines

Engine Details

To generate a mountable engine, run rails 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

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:

1
2
3
4
5
6
# my_engine/app/controllers/my_engine/application_controller.rb
 
module MyEngine
  class ApplicationController < ActionController::Base
  end
end

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:

1
2
3
4
5
# my_engine/config/routes.rb
 
MyEngine::Engine.routes.draw do
  # route stuff goes here
end

Remember, the full application’s routes start with 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

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.

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

With a full engine, the Post routes were included directly into the parent application automatically. This time, using the mountable engine and running 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}

…and that’s it. All of the engine’s functionality is bundled into the parent application underneath the route /my_engine.

After running 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>

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:

1
2
3
# parent_app/app/views/posts/index.html.erb
 
<p>Good Bye!</p>

No change and of course not! Our engine is namespaced, remember? Instead, I moved the template to 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

I then changed the parent application’s view template to use the helper.

1
2
3
4
# parent_app/app/views/posts/index.html.erb
 
<p>Good Bye!</p>
<%= test %>

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 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!