The rails engine
What is Rails Engine? A Rails Engine is a stand-alone Rails app that can be mounted by another Rails app. This is acheived largely through namespacing. Controllers and models in a Rails Engine are defined within modules of the engine’s namespace. Simlarly, assets specific to the engine are ...
What is Rails Engine?
A Rails Engine is a stand-alone Rails app that can be mounted by another Rails app. This is acheived largely through namespacing. Controllers and models in a Rails Engine are defined within modules of the engine’s namespace. Simlarly, assets specific to the engine are accessed in namespaced directories. To create engine simply run the following command.
rails plugin new engine_name
What is it used for?
Engines aren’t the answer for everything, and building a rails app with engines will increase development time. The benefits are that you can maintain pieces separately. Each engine can be run in isolation having it’s own layout, assets, and test suite. Furthermore, architecting an app with engines forces you to really think through your dependencies and makes you isolate things from the get go thus resulting in maintainable code base.
Advantages
- Reusable pieces. Because engines are just gems, they can be run inside any rails app. Reusing an engine is as easy as putting a line in a Gemfile.
gem 'engine_name', path: '/path/to/engine'
- Separate Tests. As apps grow, test suites can become unwieldy and take longer to run. With engines, test suites are broken apart and run faster. Because engines are isolated, you can be assured that changes to an engine wont affect other parts of your app.
- Quick development. It’s easier for developers understand a single engine rather than needing to understand the entire app. Rampup time is shorter.
Disadvatages
- Gems. Engines define dependancies with a .gemspec, not a Gemfile, meaning your gems need to be available on a gem server like RubyGems and can’t be loaded directly from GitHub. You can always vendor a gem locally, but thats kind of a hassle. You also need to be careful to use similar versions of dependancies between engines. If you’re using haml 3 in one engine, use that version everywhere.
- Shared assets. Sharing assets and partials between engines is hard. I’ve tried to various solutions, but ended up stubbing out the dependent assets within each dummy app. For instance, the newsletter signup form in the sidebar gets pulled in from the main application as shared/_newsletter.erb. In the dummy app of blog engine, we have a blank template under shared/_newletter.erb so it doesn’t raise a missing partial error when we run the blog in isolation.
- Migrations. Scoped migrations are great and make it clear which tables go with which engine. But running rake app:install:migrations is lame, and can lead to some nasty bugs when migrations get out of sync. It is recommended to keep your migrations separate in engine, but the down side is that running migration from the Rails main app does not running migration from engine. Fortunately there is a solution to this problem as I will show you later.
Type of engines
- Full engine: With a full engine, the parent application inherits the routes from the engine. It is not necessary to specify anything in parent_app/config/routes.rb. Specifying the gem in Gemfile is enough for the parent app to inherit the models, routes etc. The engine routes are specified as
# engine_name/config/routes.rb Rails.application.routes.draw do # routes define here end
No namespacing of models, controllers, etc. These are immediately accessible to the parent application.
- Mountable engine: The engine's namespace is isolated by default. Notice the line with isolate_namespace
# my_engine/lib/my_engine/engine.rb module MyEngine class Engine < Rails::Engine isolate_namespace MyEngine end end # my_engine/config/routes.rb MyEngine::Engine.routes.draw do # engine route define here end
And from main Rails app
# parent_app/config/routes.rb ParentApp::Application.routes.draw do mount MyEngine::Engine => "/engine", :as => "namespaced" end
The difference is that mountable engine will create the engine in an isolated namespace, whereas full engine will create an engine that shares the namespace of the main app.
To generate either types of engine use the following command
rails plugin new enigne_name --full # Full engine rails plugin new engine_name --mountable # Mountable engine
Running all engine migrations from main Rails app
As I've mentioned above regardless to problem with migration the solution is simple just put following lines of code into your engine class.
# my_engine/lib/my_engine/engine.rb module MyEngine class Engine < Rails::Engine isolate_namespace MyEngine # add these lines of code initializer :append_migrations do |app| unless app.root.to_s.match root.to_s app.config.paths["db/migrate"] += config.paths["db/migrate"].expanded end end end end
Conclusion
If you feel the pain of dealing with a bloated architecture, chances are you’ll have places where engines make sense. If you’re comfortable with Rails, Engines are just a short mental leap. It may be somewhat painful to break out your first engine, but understanding engines means In the end, you’ll have a much more maintainable and scalable systems.