Taming Rails Apps with Engines
As the engineering team was planning for the recent Socialcast release we realized we were going to be adding a couple of significant new functional areas that each had their own models, views, assets, etc. We were concerned that this could result in a sprawling code base that was hard to learn and maintain.
We came up with two ways to solve this in our Ruby on Rails application: either we could break it down into multiple applications and use API calls or we could isolate the functional areas using Rails Engines. We knew that we wanted a seamless, integrated experience that was easy to maintain so we decided to move forward with the Engines approach. It provided a lot of the benefits that we were looking for and a couple of surprise benefits that we wanted to share.
Typically, Engines are used to distribute a mini-app as a ruby gem which can then be mounted in an existing application. We wanted to do something similar, treating Projects as a mini-app, but without the hurdle of packaging a gem. We created an “engines” directory in the top level of our Rails app and put each Engine in a subdirectory.
Our first Engine was “projects” so I’ll walk you through the steps to add it. We’re using Bundler so in order to pick up the Engines we add this to our Gemfile:
gemspec :path => 'engines/projects'
Inside the “projects” directory we have our mini-app with a basic gemspec (to keep bundler happy) as well as the normal Rails directories (app, config, lib, spec, etc). If you’d like to learn more about how to build Engines there is an excellent RailsGuide and a RailsCast.
Aside from the plumbing the things we put in the engine were:
- i18n keys (config/locales/en.yml)
Once we had a working Engine we wanted to evaluate whether this had provided enough benefit to justify the complexity. The primary goal was to keep the app maintainable and easy to learn.
We felt that we had succeeded in a number of areas:
- It’s easy to understand the Projects data model because all of the model files are together.
- The “projects” folder is a natural spot for a README. This is good place to provide an overview of the functionality and data model for new developers.
- The connection between the models, controllers and assets makes it easy to see how things fit together.
Our existing team also found several benefits in using Engines:
- You can run the Projects tests independently when making changes to Projects.
- It is much easier to identify unused images, scripts, etc. when they are grouped together with the views and CSS files that reference them.
Aside from a little more depth in our directory structure there were a couple of things that were challenging about this approach.
The main problem was how to extend core models without introducing a lot of Projects-specific code. We ultimately decided to use Concerns which we then mixed into the core models. This worked really well for some things – adding associations, validations, callbacks and methods. However in some cases the most straight-forward implementation would have required a lot of “if” checks in the core code which we wanted to avoid. We decided to avoid that and add custom extension points for those cases. Later we found that we often found ways to reuse the same extension points for other modules of the system.
As we added more engines we also wanted to make it easier to add new engines. In a few places we loop over the installed engines to avoid having to add an entry to a central file for each entry. For example, here’s how we set up our Gemfile:
Dir.glob(File.dirname(__FILE__) + '/engines/*/*.gemspec').each do |engine_gemspec|gemspec :path => File.dirname(engine_gemspec)end
The engineering team has really liked the Engine changes and I’ve gotten good feedback from our recent new hires. We have gone back and extracted some of our previous components into engines and found that it’s a great way to isolate independent modules of the system to keep things understandable and maintainable. We’re currently up to nine engines and are also using them to build some of the exciting new features that are coming next.