Extracting Rails Engine by example - Vikings social media 
Tags: ruby, rails, tutorial, beginners
Published: 2019-07-01 17:19:26

---
title: Extracting Rails Engine by example - Vikings social media 
published: true
description: Step by step tutorial on how to extract Rails engine from existing Rails application.
tags: ruby, rails, tutorial, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/6pxqs6fm2yrpua5uqcb8.jpg
series: rails_engines
---

This post will describe step by step extraction process of a Rails engine from a monolithic application. This is a follow-up to my previous piece, where I explained the pros and cons of Rails engines as a solution for modularizing big monolithic applications. The full source code of application used for this post can be found [here](https://gitlab.com/szaszolak/valhalla/merge_requests/2)

## Table Of Contents
  * [Introduction](#ch0)
  * [Engine preparation](#ch1)
    * [Engine initilization](#ch1-p1) 
    * [RSpec initialization](#ch1-p2)
    * [Databse connection configuration](#ch1-p3)
  * [Parent app preparation](#ch2)
    * [Request test creation](#ch2-p1)
    * [Engine instalation](#ch2-p2) 
  * [Code transfer](#ch3)
    * [Migrations](#ch3-p1)
    * [Models](#ch3-p2)
    * [Factories](#ch3-p3)
    * [Rest of Rails parts](#ch3-p4)
    * [Not standard Rails components](#ch3-p5)
    * [External dependencies](#ch3-p6)

## Introduction <a name="ch0"/>
To set a scene, let's imagine that we are earls (aka chieftains) of Viking communities. As a forward-minded leader, we've already created a simple social media website for our people. It allows townsmen to enlist as warriors or farmers, and browse lists of each profession, to find companions or suppliers for upcoming raids. It also has a separate section where special e-seer gives its daily weather prophecies. It all works together, but now we've decided that it is time to improve architecture, and maybe share some parts of our systems with other Viking communities as an open source. With no further ado let's get to work, and extract seer module into the engine.

![](https://media.giphy.com/media/ddCPqhszsbOxzWceGo/giphy-downsized.gif)


## Engine preparation <a name="ch1"/>
Before We can move any of the existing code from the parent app into the engine, we need to initialize it, set up a unit test framework and configure development and test database connections.

### Initialize engine <a name="ch1-p1"/>
To initialize engine We can use Rails generator with this command.

```bash
 $ rails plugin new seer --mountable --dummy-path=spec/dummy --skip-test --databse=postgresql --skip-git  
```

It accepts a number of flags that adjust the generated engine scaffold to our needs. Most important ones are:
 - `--mountable`, it instructs the generator that we want a 'mountable' engine, and it should be namespace isolated from the host application. This prevents constants name collision, between the code of the host app and the engine.  
 - `--dummy-path`, sets the location of 'dummy' app generated by *Rails* for unit test purposes.
 - `--skip-test`, instructs generator to not scaffold tests, it is useful if you prefer to use another test framework over *Minitest*. For me preferred one is *Rspec* (which will be configured later on).
 - `--database`, configures database adapter which will be used. It is the best to set it, to the same as the one used by the application from which we want to extract an engine
 - `--skip-git`, prevents new *git* repository initialization. I prefer to do that at the very end of a whole process when I'm moving the engine from a subdirectory of the parent app into a separate repository. Before that, I work on a separate branch of the main repository and compare changes in the merge (aka pull) request. 

After the command is executed, the generator scaffolds the requested engine in a subdirectory of the parent app. As the generator is working we can see typical output upon the terminal. Among standard parts like controllers, models, we can also notice a few additional files has been generated

```bash
create  lib/seer.rb
create  lib/seer/engine.rb
create  seer.gemspec
create  lib/seer/version.rb
vendor_app  spec/dummy
```

Those files are really interesting for two reasons, one they clearly indicate that engine is a specialized gem, and two each of them has a special role:
 - `lib/seer.rb` is an entry point to the engine, this file will be required when the host application of the engine loads its dependencies. It can be used to make engines external dependencies configurable and loads `lib/seer/engine.rb`
 - `lib/seer/engine.rb` informs the parent application that this given gem is an engine. It also can be used to configure engine parameters (similar `config/application.rb` file is used in a standard *Rails* app). In fact, the generator already configured one parameter for us: `isolate_namespace Seer`, it declares that the whole engine is isolated in the namespace named after the engine.
 - `seer.gemspec` contains metadata about engine and gems upon which it depends.
 - `lib/seer/version.rb` this file declares the current version of the engine
 - ` spec/dummy` is previously mentioned 'dummy' Rails app which will be used in unit tests of the engine
          
### Rspec initialization <a name="ch1-p2"/>

After the engine is initialized, we should configure a unit test framework. Although there is no limitation on which one we can use, it is the most convenient to choose the same one, as it is used in the parent app. That way, we can simply reuse most of the already existing unit test code. At first, we need to add development dependencies into the `gemspec` file.

```ruby
Gem::Specification.new do |spec|  
  ....
  spec.test_files = Dir["spec/**/*"]            

  spec.add_development_dependency 'rspec-rails'
  spec.add_development_dependency 'factory_bot_rails'
  spec.add_development_dependency 'dotenv-rails'
end
```

Then we can proceed `lib/seer/engine.rb` and set up generators.

```ruby
module Seer
  class Engine < ::Rails::Engine
    isolate_namespace Seer


    config.generators do |generators|
      generators.test_framework :rspec
      generators.fixture_replacement :factory_bot
      generators.factory_bot dir: 'spec/factories'
    end
  end
end
```

And at least we should install new dependencies and *RSpec*
```bash
developer@macbook engines-directory $ bundle install
developer@macbook engines-directory $ rails g rspec:install
  create  .rspec
  exist  spec
  create  spec/spec_helper.rb
  create  spec/rails_helper.rb
```

After `rails_helper.rb` file gets created, we have to adjust the environment path in it, to point onto the 'dummy' app.
```ruby
require File.expand_path('../dummy/config/environment.rb', __FILE__)
```

And now we are ready for the next step.

### Database connection configuration <a name="ch1-p3"/>

A first step to configure 'dummy' app database connection is to adjust `database.yml` file in 'dummy' `db` directory, for that most of the `database.yml` file from the parent app can be reused, except for databases names.
```yml
default: &default
adapter: postgresql
host: <%= ENV.fetch('POSTGRES_HOST') %>
username: <%= ENV.fetch('POSTGRES_USER') %>
password: <%= ENV.fetch('POSTGRES_PASSWORD') %>
port: <%= ENV.fetch('POSTGRES_PORT') %>

development:
  <<: *default
  database: <%= ENV.fetch('POSTGRES_DB') %>_development

test:
  <<: *default
  database: <%= ENV.fetch('POSTGRES_DB') %>_test
``` 

I've decided to store sensitive data outside the *git* repository, inside environment variables. In this case, we should prepare a tool to load locally environment variables for us. I've chosen [Dotenv](https://github.com/bkeepers/dotenv), so I've created a `.env` file in 'dummy' app directory and required `dotenv` before dummy apps boots. 
```ruby
#seer/spec/dummy/config/application.rb
Bundler.require(*Rails.groups)
require "seer"

require 'dotenv-rails'

module Dummy
  class Application < Rails::Application
...
```

At least we should execute two Rails database-related tasks.
```shell
developer@macbook engines-directory $ rails db:setup
developer@macbook engines-directory $ rails db:migrate
```
If all steps are correctly performed inside the 'dummy' app `db` catalog, `schema` file should get created. 

After that, we are ready to take the next step. 
![](https://media.giphy.com/media/3o7TKD9Irma8yw5fSo/giphy.gif)

## Parent app preparation <a name="ch2"/>
With the initialized engine next, we need to plug it into the parent app, so the changes we are going to make Can be iteratively verified as we proceeded with code transfer.


### Request test creation <a name="ch2-p1"/>
Before we extract any piece of code, it will be very handy to ensure, that the whole system from an end-user perspective has not changed after the process is completed. To assure that, I prefer to add requests test for the extracted module.

```ruby
require 'rails_helper'

RSpec.describe 'WeatherProphecies', type: :request do
  let(:villager) { create(:villager) }

  before { sign_in villager }
  
  describe 'GET /weather_prophecies' do
    it 'returns current prophecy' do
      get weather_prophecies_path

      expect(response.body).to include('rain')
    end
  end
end
```
### Engine installation <a name="ch2-p2"/>
To use newly created engine we need to include it in `Gemfile` of thr parent app 
```ruby
source 'https://rubygems.org'
...

# engines
gem 'seer', path: 'seer'
```

And mount it in the routes.

```ruby
Rails.application.routes.draw do
  ...
  mount Seer::Engine, at: '/seer'  
  ...
end
```

## Code transfer <a name="ch3"/>

With the parent app and the engine ready, we can start moving pieces of code from one to another. I prefer to start from the database and continue up the layers until I got to the controllers. That way we have less external dependencies to handle at the time. 

### Migrations <a name="ch3-p1"/>
In the end, the engine and the parent app will use the same database. However for test and development purposes, and also to make the engine truly mountable, even to other apps, we have to copy all migrations related to transferred database tables into the engine. While coping migrations from the parent, **we should leave migrations names intact**, so when the engine is installed those migrations will not get duplicated. 

By default all tables of the engine are prefixed with the name of the engine (`seer` in this case) since that could not be the case before the extraction when all existing migrations are copied to the engine, we must add new ones which will rename transferred tables with the correct prefix.
```ruby
class RenameWeatherPropheciesToSeerWeatherProphecies < ActiveRecord::Migration[6.0]
  def change
    rename_table :weather_prophecies, :seer_weather_prophecies
  end
end
```

To copy and apply the migrations of the engine to the parent app, we can use the following commands.

```bash
developer@macbook host-app-directory $ rails seer:install:migrations
NOTE: Migration 20170809130629_create_weather_prophecies.rb from seer has been skipped. Migration with the same name already exists.
developer@macbook host-app-directory $ rails db:migrate
```

### Models <a name="ch3-p1"/>
To transfer models from the parent into the engine, we can simply run the bash command, or use the cut and paste method.

```bash
developer@macbook host-app-directory $ mv app/models/weather_prophecy.rb seer/app/models/seer/weather_prophecy.rb
```

We should be careful to place each file in the correct directory, which represents the namespace of the engine (eg: **seer/app/models/seer**) otherwise, an autoloader will ignore them and we will get an error `NameError: uninitialized constant`.

After each model with the corresponding spec is transferred, we need to wrap their code in the module of the engine.
```ruby
module Seer
  class WeatherProphecy < ApplicationRecord
  end
end
```
### Factories <a name="ch3-p3"/>
When models and their specs are moved, the one thing we lack to make test back to the green state are factories.

To use [FactoryBot](https://github.com/thoughtbot/factory_bot_rails) at first we need to adjust `rails_helper.rb` file and point set a path to a directory which will contain factories definitions
```ruby
require 'factory_bot'
FactoryBot.definition_file_paths = [File.join(File.dirname(__FILE__), 'factories')]
FactoryBot.find_definitions

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
  ...
end
```

Then we can move factory definitions just like we've done with the model files.

### Rest of Rails parts <a name="ch3-p5"/>
The rest of the *Rails* parts like controllers, jobs, channels can be moved the same way as we've transferred models.

As for the routes, they should be cut out from the `routes.rb` file of the parent app and placed in the one that belongs to the engine.

### Not standard Rails components <a name="ch3-p6"/>
In this scenario I've created an additional `services` layer, there is one gotcha with such an approach. To ensure that autoloader loads these parts of the code as well we can do one of two things, either we extend autoloader paths, or we place these additional layers in the app directory of the engine. I've chosen the latter for this scenario.

Besides that, we can move them like the rest.

### External dependencies <a name="ch3-p8"/>
The last finishing touch is to make external dependencies configurable.
Although while developing the engine, we could refer to parents app modules and classes, by using their qualified names (prefixed with `::`).
```ruby
module Seer
  class ApplicationController < ::ApplicationController
  end
end

```

Unfortunately, this introduces thigh coupling between the engine and the parent app. To address that, and make our engine more flexible we should allow others to configure external dependencies upon engine initialization.

Because of that, we should add a module accessor which would store such dependencies.
```ruby
# lib/seer.rb
module Seer
  mattr_accessor :application_controller_class

  def self.application_controller_class
    @@application_controller_class.constantize
  end
end

```

To configure them, we need to create an initializer in the parent app.

```ruby
# parent_app/config/initializers/seer.rb

Seer.application_controller_class = 'ApplicationController'
```

Inside the engine, we can use them in the following manner.
```ruby
  class ApplicationController < Seer.application_controller_class
```

After this last step, our engine is ready to be used. We could leave it as a subdirectory of parents app, or move it to stand alone repository and use it freely in a number of apps. 

As we've just accomplished this challenging task our system is better structuralized and easier to navigate, a test suite is running faster, it is easier to work in parallel on different parts of the system without worry about merge conflicts. We are now ready to celebrate our glory on a Viking feast.  
![](https://images.gr-assets.com/hostedimages/1423297125ra/13604922.gif)