How to Build a Rails Engine
Rails engines range from simple plugins to powerful micro-applications. The discussions we’ve had so far about Railties are closely related to the function of a Rails engine. One interesting side note is that a Rails application is a Rails engine itself — so it’s easy to see how we can encapsulate just about any normal Rails functionality in an engine, to ultimately embed in a host application.
The Rails engine documentation is well written and touches on the many ways to include functionality. I won’t cover every detail of Rails engines in this chapter, just enough to get you started making use of them. It’s possible to make full applications (routes, controllers, models, migrations, etc.) using Rails engines. However, we’re going to focus on some of the simpler the elements of a Rails engine that allow us to integrate functionality where a Railtie won’t suffice. Just know, there is far more you can do with Rails engines than what we’ll cover here. The documentation link above provides examples of many of those use cases.
I wrote a blog post about rendering relatives timestamps in Rails on the client. Using a client-side solution like timeago.js allows timestamps to update in real-time, so when we see “15 minutes ago”, we know the record really is 15 minutes old.
The other benefit to using a client-side library like
For this example, we’ll make a gem that integrates
timeago.js in to a Rails application and provides a simple way to render relative timestamps in a view.
The implementation of our gem will require:
- Adding the
The third requirement was covered in a previous chapter on view helpers, so we’ll cruise through that one quickly. However, including assets hasn’t been covered and it’s the perfect segue in to the benefits of using a Rails engine, rather than just a Railtie.
Rails engines allow us create the same directory structure of a Rails application, since a Rails application is just a Rails engine. By indicating our gem is an engine (we’ll see how to do this soon…), Rails will pick up the related files in the Rails-specific directories in our gem. So if we add a file in the
app/helpers/ directory of our gem, that same file will be available in the host Rails application. The same approach applies for controllers, models, migrations, assets and anything else we would add to a typical Rails application.
To start, let’s create our new gem:
vendor/ directory is the most appropriate place for them. Let’s create the directory
timeago.js plugin there:
To create our view helper, we’ll add the
The code for the view helper is shown below:
timeago.js plugin on page change (this includes the initial page load). This is almost identical to the first step of including the vendored
timeago.js asset, except we’re going to put it in the
Let’s create the directory
This file serves as both a manifest file for the
jquery.timeago.js asset and a function to invoke the plugin whenever the page loads or changes.
Lastly, we need to designate our gem as an engine. The default entry file that was created when we used bundler to bootstrap our gem looked like this:
All we need to do is add the
Engine class and inherit from
Rails::Engine, giving us:
At this point, because our gem is so closely tied to Rails, we should add Rails as a dependency in our gemspec:
Note: In addition to adding Rails as a dependency, we’ve also specified that it’s only compatible with Rails version
3.1 or later because of the need for the asset pipeline.
Moving to a sample Rails application, we can include the gem in our host application by adding it to the
Gemfile using the path option:
Since we included an asset that needs to be included in the Rails asset pipeline, we have to take one more step and instruct the user to add the following to their
This directive actually refers to the
Now when we load our Rails application, tags using the
timeago view helper get rendered to UI as:
Implementations in the Wild
One of the greatest examples of making the most of a Rails engine is Devise. Devise is one of the more popular options for adding authentication to a Rails application. Just looking at the app directory of the gem, we can see Devise adds functionality through controllers, helpers, mailers and views. The structure of Devise is fairly complicated because it is doing so much, but here is where the Rails engine is defined allowing the elements in the
/app directory (among other things) to be integrated in to a Rails application.
The introduction of Rails engines created a new way to organize micro-applications and integrate them into a host application. Doing so keeps features and otherwise separate logic truly separate.
I’ve only scratched the surface on what a gem can provide through engines. It’s certainly more detailed than just asset and view helper integration, as shown above. If you’re interested in learning more about the other features Rails engine provide, the Rails guides are a great place to start.