I came across an interesting challenge a few days ago. We have a number of Rails application in our system, architected differently, using different gems, doing different things. However, across the board, they all maintain a single piece of shared functionality – the ability to share their status.

What do we want?

  • The ability to share the fact that they’re running
  • The ability to share the version of the code that they’re running

No matter the purpose of each individual app, they each share this functionality. And this functionality is completely decoupled from the rest of the app. So why not DRY it up?

I tried a number of approaches to this problem. I tried monkeypatching ActionController, re-drawing Rails routes dynamically, and even copy+pasting the controller. None of these approaches worked well and were an acceptable solution to me. Eventually, I stumbled upon Rails Engines, and realized this was the best solution to this problem.

What are Rails Engines?

Engines are mini-Rails apps (typically bundled as a Ruby gem) that provide functionality to the parent app. An engine’s directory structure mimics the structure of a Rails app. Any controller you add to app/controllers, any model you add to app/models, any view you add to app/views will be available to the parent app. Chances are, you’ve used a Rails engine in your application before. devise is a popular one.

How do I create a Rails engine?

Creating a Rails engine is as simple as including the rails gem in your gemspec, creating a new class and extending from Rails::Engine. However, Rails offers a great tool to automatically create an Engine gem and set up the directory layout.

rails plugin new v8 --mountable

This will create a number of directories and files, but these are the ones you need to know:

  • app - Just like the app directory in your Rails app. This is where you put your Engine controllers, models, views, helpers, mailers, etc.
  • config/routes.rb - Just like the routes.rb file in Rails. Here, you define your Engine specific routes. Later, in your Rails app’s routes.rb, you can choose to mount this engine and your app will have all of the routes you define here.
  • lib - This is the lib folder like in any gem. Here, you can configure your Engine, and add any non-Rails code that you need.

Take a look at lib/v8/engine.rb. You’ll see the line isolate_namespace V8. This line is extremely important because it namespaces everything in your gem under your gem name. Without this line, every single model and controller will be namespaced under the base namespace, so you may experience collisions if your Rails app defines models or controllers with the same name.

How do I share functionality?

The purpose of this post is to teach you how to share functionality between Rails apps using Engines, so lets get to the point!

We want to define a simple controller, PingController, that accepts a GET request to /ping and returns “pong”.

app/controllers/ping_controller.rb

module V8
	class PingController  ''`

Now, run `rake routes` again...

$ rake routes
Prefix Verb URI Pattern Controller#Action
v8 / V8::Engine

Routes for V8::Engine:
ping GET /ping(.:format) v8/ping#ping

Much better! Now we have a fully functional PingController and its route added to our Rails application, by installing 1 gem and adding 1 line to `routes.rb`. Engines are an extremely powerful way to separate functionality and reuse code across applications.