Intro to Rails Engines
I have recently been hearing quite a bit about people using Rails engines. I was intrigued about how they are used and how they integrate into a Rails application, and I soon got a chance to satisfy my curiosity by working on a couple projects that used these engines. Here I will talk a little about what Rails engines are used for and how they are integrated into a Rails application.
What’s a Rails Engine?
So the first question you may be asking is, what the heck is a Rails engine, anyway? In short, it allows you to wrap up a Rails application or a subset of its functionality in a way that makes it easy to share it with other applications. This plug-and-play sort of functionality is quite similar to how Ruby gems work. In fact, prior to Rails 3.0, all Ruby gems automatically behaved as engines, and since Rails 3.0 all Rails applications are just engines. And just like a Rails application, a Rails engine is also a Railtie, so it has access to rake tasks and generators that can be used in the engine. Since Rails 3.0 has come out, if you want a gem to automatically behave as a Rails engine, you have to define an engine somewhere inside of the gem’s lib folder.
module MyEngine class Engine < Rails::Engine end end
Types of Engines
When creating a Rails engine, there is one special thing to take note of, and this is the difference between a full engine and a mountable one. In a full engine, the parent application inherits all of the routes defined in the engine, so it is not necessary to define anything in parent/config/routes.rb. Simply specifying the Engine in the gem file of the parent application is enough for it to have access to all of the engines models, controllers, routes, etc. For a mountable engine, its namespace is isolated by default:
module MyEngine class Engine < Rails::Engine isolate_namespace MyEngine end end
The routes of the engines are namespaces and it needs to be mounted in the parent app’s routes file to be used.
Parent::Application.routes.draw do mount MyEngine::Engine => '/engine', as: 'my_engine' end
Once you have your engine mounted inside of the parent application, some helpers are defined to help specify the routes. Inside of the parent app above there would he a helper named ‘my_engine’ that would allow you to access its routes like:
From the engine, there is also a helper named ‘main_app’ that is defined that allows the engine to access the parent app’s routes.
MIGRATIONS AND SEEDING DATA
The parent application also needs to be able to install the migrations from the Rails engine. In order to copy over the migration files from the engine to the parent app you can run:
A migration would be skipped if one with the same name already exists within the application. Once the migrations have been copied over they can be loaded by running
If the engine has any seed data in the db/seeds.rb file it can be loaded by running
Finally, testing a Rails engine is a little different than just testing a Rails application. Inside of your test directory there is a dummy app, which is a Rails application that is used during testing. Your engine is automatically loaded into the dummy app and all of your tests are run from the dummy app’s environment. If you find it necessary, you can generate controllers, models or views in the dummy app to use while testing your engine. Special consideration must be taken when writing controller tests. Since the requests are going through the dummy application you need to be more specific when using the get, post, etc. methods. This is where the :use_route option comes in for in these methods.
get :index, use_route: :my_engine
This will perform a get request to the index action of the controller, but it specifies that you want to use the routes from my_engine to get there rather than those in the dummy application.
Working with Rails engines has been an insightful experience and I have learned quite a bit about how they work. It is always fun to find out about new technologies to use, and I’m looking forward to continuing my exploration of Rails Engines and finding uses for them in my future projects.