This is a blog of AmberBit - a Elixir and Ruby web development company. Hire us for your project!

Rails mountable engines

Kdziemianowicz

Posted by Kamil Dziemianowicz

Kamil, long-term employee of AmberBit (joined 2012), is a full stack developer (Ruby, JavaScript).
kamil.dziemianowicz@amberbit.com | @dziemian007

Introduction

What is an engine? And a mountable one? In Rails world it’s just a piece of code, which plugged to existing application, extends its basic functionality. Good examples would be: ActiveAdmin, AmberBit’s Translator or Devise. Also Rails application is example of an engine.

Really nice use for engine that comes to my mind would be API versioning. A website would have no API at all on start. One instance of it would be deployed with /api/v1 mounted, and other /api/v2/. Clean, easily separable and providing only functionality that is needed in given case.

There is also detailed guide available on RailsGuides.

Getting started

All you really need is to create new directory for your engine, let’s say foo_baz, .gemspec file and a engine.rb file within lib/foo_baz directory. The key thing here is to inherit from ::Rails::Engine.

module FooBaz
  class Engine < ::Rails::Engine
    isolate_namespace FooBaz
  end
end

And to mount it into existing Rails application, add newly created library to Gemfile:

...
gem 'foo_baz', path: 'lib/engines/foo_baz'
...

Nothing unusual in foo_baz.gemspec file as well. Very basic, minimal content would be for example:

$:.push File.expand_path("../lib", __FILE__)

Gem::Specification.new do |s|
  s.name        = "foo_baz"
  s.version     = "0.0.1"
  s.authors     = ["Kamil Dziemianowicz"]
  s.summary     = "Summary of FooBaz."

  s.files = Dir["{app,config,db,lib}/**/*"]

  s.add_dependency "rails", "~> 4.2.4"
end

Of course fully-featured engine will be much more complex. Rails has built-in generator which will create dummy structure & files for your engine and even more. To run it, simply invoke rails plugin new foo_bar with option --mountable. It will generate directory structure not surprisingly similar to standar Rails application structure.

create
create  README.rdoc
create  Rakefile
create  foo_bar.gemspec
create  MIT-LICENSE
create  .gitignore
create  Gemfile
create  app
create  app/controllers/foo_bar/application_controller.rb
create  app/helpers/foo_bar/application_helper.rb
create  app/mailers
create  app/models
create  app/views/layouts/foo_bar/application.html.erb
create  app/assets/images/foo_bar
create  app/assets/images/foo_bar/.keep
create  config/routes.rb
create  lib/foo_bar.rb
create  lib/tasks/foo_bar_tasks.rake
create  lib/foo_bar/version.rb
create  lib/foo_bar/engine.rb
create  app/assets/stylesheets/foo_bar/application.css
create  app/assets/javascripts/foo_bar/application.js
create  bin
create  bin/rails
create  test/test_helper.rb
create  test/foo_bar_test.rb
append  Rakefile
create  test/integration/navigation_test.rb

Most important parts of it are:

  • foo_bar.gemspec - has the same function as you would expect from gem’s .gemspec file
  • lib/foo_bar.rb - FooBar module definition
  • lib/foo_bar/engine.rb - mentioned before Engine file
  • config/routes.rb - defining routes engine will provide, same as Rails app

Also noticable things are similar to Rails application application_controller.rb and application_helper.rb, both namespaced into FooBar module. There is folder for image assets. Analogously, JavaScript and CSS files would go to app/assets/javascripts/foo_bar/* and app/assets/stylesheets/foo_bar/* folders. Views shoud be put into app/views/foo_bar/* folder and so on… The rule of thumb is that everything should be namespaced, and if it’s namespaced, it should be put where it usually goes in Rails app, but including foo_bar subfolder.

Isolate namespace

Very important part of engine class definition is declaration of namespaces isolation:

...
isolate_namespace FooBar
...

It will force scoping all classes (AR models, controllers, helpers etc…) into FooBar module. Thereby both base application and engine library could define User class, with corresponding database table, separated views, and they won’t clash. Of course it is not mandatory to isolate engine from base application. It could be useful when created engine won’t be used by other people, or developer is aware of all classes and routes defined in it. Generally, better keep them namespaced.

Rails generator

Created engine comes with executable bin/rails script. Its main purpose is to help generate proper models/views/controllers. Keep in mind, that all engine resources should be namespaces inside engine module name. To run sample generator:

$ bin/rails g model kitty name:string

In result, proper folders structure and class-module hierarchy will be preserved:

...
invoke  active_record
create    app/models/foo_bar/kitty.rb
...

And content of Kitty model file:

module FooBar
  class Kitty < ActiveRecord::Base
  end
end

Tie things up

Let’s start with routes. Most likely you’d like your engine to add new pages with own paths to base app. To achieve this, simply amend routes.rb file with new definitions:

FooBar::Engine.routes.draw do
  resources :kitties
end

In order to activate those routings in base application, it’s routes.rb file also needs to be amended as follows:

Rails.application.routes.draw do
  ...
  mount FooBar::Engine, at: "/foo_bar"
  ...
end

To see it actually works, let’s check all available routes with $ rake routes command:

 Prefix Verb URI Pattern Controller#Action
foo_bar      /foo_bar    FooBar::Engine
   root GET  /           pages#home

Routes for FooBar::Engine:
   kitties GET    /kitties(.:format)          foo_bar/kitties#index
           POST   /kitties(.:format)          foo_bar/kitties#create
 new_kitty GET    /kitties/new(.:format)      foo_bar/kitties#new
edit_kitty GET    /kitties/:id/edit(.:format) foo_bar/kitties#edit
     kitty GET    /kitties/:id(.:format)      foo_bar/kitties#show
           PATCH  /kitties/:id(.:format)      foo_bar/kitties#update
           PUT    /kitties/:id(.:format)      foo_bar/kitties#update
           DELETE /kitties/:id(.:format)      foo_bar/kitties#destroy

Notice how engine path is mounted under defined /foo_bar part. This allows having the same resources names in both main Rails application and mounted engine. The tricky part about routes is accessing base application routing helpers in engine and vice versa. Imagine engine view, where we want to include link to root path /. Usually you would just write:

<%= link_to "Home", root_path %>

But you’ll end up with familiar screen and error message

Showing /home/kaa/amberbit/blog/engine/foo_bar/app/views/foo_bar/kitties/index.html.erb where line #1 raised:

undefined local variable or method `root_path' for #<#<Class:0x000000048abed0>:0x000000048ab318>

Why is that? Simply because FooBar engine doesn’t define own root path, and don’t know about base application routing. To access it, all helpers must prefix helpers with main_app:

<%= link_to "Home", main_app.root_path %>

When using engine paths helpers from engine itself, you don’t need to prefix it. And analogously, don’t need to prefix base app routes in app itself, but that’s kinda obvious.

And how can I reference engine routes from main app? Using foo_bar prefix of course!

<%= link_to "Kitties", foo_bar.kitties_path %>

If engine is not namespaced, its routing proxy prefix would be foo_bar_engine instead of foo_bar.

Tip for curious ones: main_app and foo_bar are ActionDispatch::Routing::RoutesProxy instances.

When dealing with many engines, it sometimes could be troublesome to mount them all in app routes. To ease this task, engine could actually mount itself. But keep in mind that this fact will be hidden from developer at first glance:

module FooBar
  class Engine < Rails::Engine
    isolate_namespace FooBar

    initializer "foo_bar", before: :load_config_initializers do |app|
      Rails.application.routes.append do
        mount FooBar::Engine, at: "/foo_bar"
      end
    end
  end
end

We used initializer to hook up into configuration before application runs.

Controllers inside engine

There is nothing magical happening in engine’s controller. The only thing to remember is to wrap them into correct module (FooBar in this example), and following hierarchy pattern put it in app/controllers/foo_bar/ subdirectory. Created views should go to app/views/foo_bar/[resource]/ subdirectory.

Generated engine comes with own ApplicationController. Nice thing to do would be amending it to inherit from base application’s ApplicationController instead of standard ActionController::Base, so engine will have access to its helpers methods, trigger before_actions and inherit all other logic. To do that, just point to not scoped ApplicationController:

module FooBar
  class ApplicationController < ::ApplicationController
  end
end

Another useful tip would be to re-use base application layout. As we have seen in first paragraph, generated engine comes with own layout file and assets manifests files. So when it’s possible to easily separate engine pages from base app by providing different layout, it’s often desirable not to do so. It’s just matter of declaring it correct way:

module FooBar
  class ApplicationController < ::ApplicationController
    layout "application"
  end
end

But wait, the default for engine is also called application. The significant difference is (again) namespacing, and originally it would be:

...
layout "foo_bar/application"
...

Defining models & database tables

Because it is possible to have the same class names in base application and inside engine, database tables needs to be somehow distinguished. Let’s take a look at model scaffolding again:

$ bin/rails g model kitty name:string

invoke  active_record
create    db/migrate/20151011103843_create_foo_bar_kitties.rb
create    app/models/foo_bar/kitty.rb
invoke    test_unit
create      test/models/foo_bar/kitty_test.rb
create      test/fixtures/foo_bar/kitties.yml

First hint is migration file name. And its content:

class CreateFooBarKitties < ActiveRecord::Migration
  def change
    create_table :foo_bar_kitties do |t|
      t.string :name

      t.timestamps null: false
    end
  end
end

Table name got prefixed with foo_bar_. We could have expected that. While not so obvious, it’s also configurable option. To do that, simply edit lib/foo_bar.rb module definition in following manner:

module FooBar
  def self.table_name_prefix
    "cute_"
  end
end

Rails generator won’t pick that change though, and we would have to manually amend all create_table migrations to reflect this. Let’s see it in action. In Rails console:

> FooBar::Kitty.all
  FooBar::Kitty Load (0.3ms)  SELECT "cute_kitties".* FROM "cute_kitties"
SQLite3::SQLException: no such table: cute_kitties: SELECT "cute_kitties".* FROM "cute_kitties"
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such table: cute_kitties: SELECT "cute_kitties".* FROM "cute_kitties"
...

Other very important point to notice is that base application has no knowledge of engine migrations. Rails preferred way of dealing with it is to copy over migration files to db/migrate. And there are even tasks helps doing just that:

$ rake foo_bar:install:migrations

Copied migration 20151011105543_create_foo_bar_kitties.foo_bar.rb from foo_bar

There is also other, more generic task, which will copy all not-yet-present migrations from mounted engines:

$ rake railties:install:migrations

You may sometimes forget to do it, and then pondering why your application is throwing exceptions. Luckily, there is a walkaround involving initialized tuning we’ve already seen in one of previous paragraphs. Just put following code in engine.rb file:

module FooBar
  class Engine < Rails::Engine
    ...

    initializer "foo_bar", before: :load_config_initializers do |app|
      ...

      config.paths["db/migrate"].expanded.each do |expanded_path|
        Rails.application.config.paths["db/migrate"] << expanded_path
      end
    end
  end
end

It will push all engine migration files into base application migrations list. As simple as that. And running migrate will also check engines for pending migrations:

$ rake db:migrate
== 20151011103843 CreateFooBarKitties: migrating ==============================
-- create_table(:foo_bar_kitties)
   -> 0.0008s
== 20151011103843 CreateFooBarKitties: migrated (0.0008s) =====================

Loading files

What is loaded out of the box? Everything from engine you would expect from Rails application will be loaded on boot. That includes models, controllers, helpers, routes, locale files (config/locales/*) and tasks (lib/tasks/*). But what about custom directories you would probably have, like app/services, app/workers for instance? They needs to be loaded manually. And a good place to do it automatically is, once again, engine.rb. I prefer splitting this task though. List of required files goes to lib/foo_bar.rb module definition file:

module FooBar
  ...
  def self.load_files
    [
      "app/workers/mailer_worker",
      "app/services/kitty_washer"
    ]
  end
end

and loading task to lib/foo_bar/engine.rb:

initializer "foo_bar", after: :load_config_initializers do |app|
  FooBar.load_files.each { |file|
    require_relative File.join("../..", file)
  }
end

Once again, app initializer is used.

Extending base application classes

Other thing your engine may want to do is to extend existing base application classes functionality. Quick & dirty way is to use Ruby class_eval and instance_eval functions. Given User class being defined in application, you could do lots of interesting stuff, but that’s just regular Ruby. F.ex. create user.rb file in app/models directory with:

User.class_eval do
  attr_accessor :name
  has_many :kitties, class_name: FooBar::Kitty
  User::MAX_KITTIES = Float::INFINITY

  def cutest_kitty
    kitties.sample
  end
end

Also remember to declare class constants in module scope (User::MAX_KITTIES), otherwise it will be declared as toplevel constant. I learned it hard way:

$ rails c
> User::MAX_KITTIES
(irb):2: warning: toplevel constant MAX_KITTIES referenced by User::MAX_KITTIES
 => Infinity

Of course, you could take more elegant approach and use ActiveSupport::Concern, but that’s topic for whole new article. Just remember to load such files manually (engine won’t do it as they live outside foo_bar subdirectory).

Engine own executables

Adding executable scripts is not something entirely tied to an engine. Any gem library could do that, but I found it very useful and it’s worth mentioning. Add extra line to .gemspec and any executable file placed in engine bin/ directory will be available for base application. No need for manual includes this time.

Gem::Specification.new do |spec|
  ...
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  ...
end

Similar applies to Rake files, anything put in engine’s lib/tasks folder will be runnable from base application, and furthermore rake -T will list those tasks. For start, engine generator creates one dummy .rake file with name corresponding library name.

$ rake -T
...
rake some_task:buy_kitties              # Buy only cute kitties
rake foo_bar:buy_kitties                # Buy moar kitties
rake foo_bar:install:migrations         # Copy migrations from foo_bar to application
...

Conclusion

Rails engine is a broad topic. Everything related to Rails application could be applied here as well. I tried to focus on some non-standard tricks I picked up when writing one. What do you think? Maybe you also have some good practises worth sharing? Any constructive criticism is welcome :)

Hubert

Hi there!

I hope you enjoyed the blog post. Can we help you with Elixir or Ruby work? We are looking for new opportunities at the very moment, and we do have team available just for you.

Email me at: contact@amberbit.com or use the contact form below.

- Hubert Łępicki

comments powered by Disqus

Want to get in touch? Drop us a line!