Ruby on Rails is a popular web framework known for rapid development backed by a great community. This post is intended to introduce and encourage you to build a more modular rails application. I maintain a collection of resources if you’d like to learn more.

Creating the Engine

Most applications require user authentication so I think that would be a great first engine to create. I’m going to walk you through the creation of ConnectBy.

Note: Credit for the naming conventions here goes to Vlad and the Evil Martians. You can check out his talk at RailsConf 2020.

The Host

I’ll refer to our rails app as the host application.

rails new host_app --database postgresql

We will install Devise in our ConnectBy engine. Let’s get started.

Generate the plugin

Our engines are installed locally in /engines and gems in /gems. Run the rails plugin generator from the host’s root.

(using whitespace for clarity)

./host_app $ rails plugin new engines/connect_by
              --database postgresql

Flags explained

Skip setting up test_unit and create a dummy app that we will later use with rspec.


Our host app will dynamically load all of our engines.


Our frontend won’t live in our engine.


Version Management

Add a rails-version file to our host app so each engine can reference it.

./host_app $ echo "" > .rails-version

Update the gemspec

Load our engine in the host application’s gemfile.

# Gemfile

Dir.glob(File.expand_path("../engines/*", __FILE__)).each do |path|
  gem File.basename(path), path: path

Install Devise

Review devise’s latest getting start instructions and use their devise inside a mountable engine wiki as an additional resource.

Add devise to our engine’s gemfile.

# engines/connect_by/Gemfile
gem "devise", "~> 4.7.1"

Load devise.

# engines/connect_by/lib/connect_by.rb

require "connect_by/version"

module ConnectBy

require "devise"
require "connect_by/engine"

Bundle connect_by and our host.

./host_app $ bundle install
./host_app/engines/connect_by $ bundle install

Install devise and create our user model.

./host_app/engines/connect_by $ rails generate devise:install
./host_app/engines/connect_by $ rails generate devise user

Update our devise configuration.

# engines/connect_by/initializers/devise.rb
config.parent_controller = 'ConnectBy::ApplicationController'
config.router_name = :connect_by

Install our new migrations on the host application.

./host_app $ rails connect_by:install:migrations

Migrate your host app’s database.

./host_app $ rails db:create db:migrate

Mount our engine’s route in the host.

# config/routes.rb

Rails.application.routes.draw do
  mount ConnectBy::Engine, at: "/a"

We are creating an isolate namespaced engine so we need to tell devise that we are using their controllers in their module.

# engines/connect_by/config/routes.rb
devise_for :users,
  class_name: "ConnectBy::User",
  module: :devise


I’ll drop the frontend from ConnectBy to avoid taking away from the tutorial. Isolated frontends with Webpacker 4 can get very involved (How to use webpacker from within engines?).

Remove assets and view layouts from the engine.

./host_app/engines/connect_by $ rm -rf ./app/assets
./host_app/engines/connect_by $ rm -rf ./app/views/layouts

Start up the server and navigate to /a/users/sign_in to confirm it worked.


We took a modular approach and created a rails engine for our user account’s.

The source code for this part is on github.

Next Steps

Part 2 focuses on setting up the test suite, specifically rspec.

Useful Resources

I have compiled a list of useful resources for rails engines and the modular monolith architecture.

Scale With Rails Engines

Need help scaling your rails application with a modular monolith? Talk to us