More at rubyonrails.org:

Rails Engines Overview

In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and easy-to-use interface.

After reading this guide, you will know:

  • What is an engine.
  • How to generate an engine.
  • How to build features for the engine.
  • How to hook the engine into an application.
  • How to override engine functionality in the application.
  • How to avoid loading Rails frameworks with Load and Configuration Hooks.

1. What are Engines?

Engines can be thought of as miniature applications that encapsulate specific functionality and integrate with a larger Rails application. An engine extends a plugin, with the Rails::Engine class inheriting behavior from Rails::Railtie.

Some examples of engines in action:

The main application is always the final authority in a Rails environment. While engines can extend or enhance the application's functionality, they are meant to support the app — not override or redefine its behavior. Engines exist to serve the application, not the other way around.

1.1. Inheritance Hierarchy

At the base of this hierarchy, Railtie is the core building block of the Rails framework. It provides hooks into the Rails initialization process and allows extensions, such as frameworks (e.g. Active Record, Action Mailer) or third-party libraries, to tie into the Rails boot sequence.

The Rails Engine builds on Railtie by adding support for things like routes, isolated namespaces, and load paths, making it possible to package complete Rails components. Engines and applications also share a common directory structure.

While an engine is packaged like a miniature Rails application, it runs inside a host Rails application. The host Rails::Application coordinates boot, executes engine initializers, and builds the overall middleware stack. Engines can also define their own configuration and contribute middleware, but these take effect when the host application boots.

1.2. Engines and Plugins

Engines are also closely related to plugins - all engines are plugins, but not all plugins are engines. The two share a common lib directory structure, and are both generated using the rails plugin new generator.

While a plugin is generated using rails plugin new <plugin_name>, an engine is generated using either:

rails plugin new <engine_name> --full
# or
rails plugin new <engine_name> --mountable

The --full option tells the generator to create an engine with its own models/controllers that share the host app’s namespace. The --mountable option creates a fully isolated, mountable engine with its own namespace.

You can read more about the different generator options in the Rails Plugins Generator Options section.

1.3. Using an Engine

You add an engine to your host application's Gemfile, and depending on how the engine is designed, you may need to mount it in the main app's routes. Mounting is required when the engine provides its own routes, as this makes any routes, controllers, views, or assets defined in the engine available at that mount point in the host application. This is covered later in the section Using the Engine in a Host Application.

Some engines, however, don't need to be mounted. These are often backend-only engines, such as those that provide Active Record models, rake tasks, or other internal functionality without exposing routes. In these cases, adding the gem to your application's Gemfile is enough to make its features available.

When an engine is mounted, it has an isolated namespace. This means the host application and the mounted engine can have a routing helper with the same name (such as articles_path) without clashing. Along with this, controllers, models, and table names are also namespaced. You'll see how to do this later in the Routes section.

2. Generating an Engine

In the following example, you will be building an engine, called "blorgh" that provides blogging functionality to its host applications, allowing for new articles and comments to be created. It will use the --mountable option to generate the engine.

To generate an engine, you will need to run the plugin generator and pass it options as needed. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal:

$ rails plugin new blorgh --mountable

The --mountable option allows the engine to behave like a self-contained mini-application that can be easily integrated into a host Rails application without polluting its global namespace. It will:

  • namespace all controllers, routes, views, helpers, and assets under the Blorgh module, preventing conflicts with similarly named components in the host app.
  • isolate routing to the engine, allowing you to mount it at a specific path in the host app (e.g., /blorgh), while keeping its internal route structure independent.
  • make the engine more modular and reusable, so it can be plugged into different applications with minimal configuration.

2.1. The Directory Structure

The structure of the --mountable engine will be as follows:

blorgh/
├── app/
│   ├── assets/
│   │   ├── images/
│   │   │   └── blorgh/
│   │   └── stylesheets/
│   │       └── blorgh/
│   │           └── application.css
│   ├── controllers/
│   │   ├── concerns/
│   │   └── blorgh/
│   │       └── application_controller.rb
│   ├── helpers/
│   │   └── blorgh/
│   │       └── application_helper.rb
│   ├── jobs/
│   │   └── blorgh/
│   │       └── application_job.rb
│   ├── mailers/
│   │   └── blorgh/
│   │       └── application_mailer.rb
│   ├── models/
│   │   ├── concerns/
│   │   └── blorgh/
│   │       └── application_record.rb
│   └── views/
│       └── layouts/
│           └── blorgh/
│               └── application.html.erb
├── bin/
│   ├── rails
│   └── rubocop
├── config/
│   └── routes.rb
├── lib/
│   └── tasks/
│       └── blorgh_tasks.rake
│   ├── blorgh/
│   │   ├── engine.rb
│   │   └── version.rb
│   ├── blorgh.rb
├── test/
│   ├── controllers/
│   └── dummy/
│       ├── app/
│       ├── bin/
│       ├── config/
│       ├── log/
│       ├── public/
│       ├── storage/
│       ├── tmp/
│   ├── fixtures/
│   │   └── files/
│   ├── helpers/
│   ├── integration/
│   │   └── navigation_test.rb
│   ├── mailers/
│   ├── models/
│   ├── blorgh_test.rb
│   ├── test_helper.rb
├── Gemfile
├── Rakefile
├── README.md
├── blorgh.gemspec

It provides the following:

  • An app directory tree
  • A config/routes.rb file:

    Rails.application.routes.draw do
    end
    
  • A file at lib/blorgh/engine.rb, which is identical in function to a standard Rails application's config/application.rb file:

    # lib/blorgh/engine.rb
    
    module Blorgh
      class Engine < ::Rails::Engine
        isolate_namespace Blorgh
      end
    end
    

--mountable engines, like above, contain some files that are not present in the --full option, these are:

  • A namespaced ApplicationController stub
  • A namespaced ApplicationHelper stub
  • A layout view template for the engine
  • Namespace isolation to config/routes.rb:

    Blorgh::Engine.routes.draw do
    end
    
  • Namespace isolation to lib/blorgh/engine.rb as described above.

Additionally, the --mountable option tells the generator to mount the engine inside the dummy testing application located at test/dummy by adding the following to the dummy application's routes file at test/dummy/config/routes.rb:

mount Blorgh::Engine => "/blorgh"

A full list of options for the plugin generator can be seen by running:

$ rails plugin --help

2.2. Core Engine Setup

2.2.1. The .gemspec File

At the root of your engine, you’ll find a file named blorgh.gemspec. This file defines your engine as a gem. It includes metadata like the gem name, version, authors, dependencies, and which files to include when it's packaged.

To use this engine in a host Rails application, you reference it in the app’s Gemfile like so:

gem "blorgh", path: "../blorgh"

Then run:

bundle install

This tells Bundler to treat the engine as a gem and load it accordingly.

Instead of publishing an engine as a standalone gem, you can generate an engine inside your application and deploy it for use by that application only. For example, placing the engine under engines/blorgh and referencing it in your Gemfile allows you to keep it within the same codebase.

2.2.2. The Engine Entry Point: lib/blorgh.rb:

Bundler looks for a file matching the gem name, lib/blorgh.rb. This file is the main entry point of the engine. It typically requires the engine definition and sets up a base module:

# lib/blorgh.rb
require "blorgh/engine"

module Blorgh
end

Some engines choose to use this file to put global configuration options for their engine. It's a relatively good idea, so if you want to offer configuration options, the file where your engine's module is defined is perfect for that. Place the methods inside the module and you'll be good to go.

2.2.3. The Engine Class Definition: lib/blorgh/engine.rb

This file defines the engine class and tells Rails how to load and isolate it:

# lib/blorgh/engine.rb
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh
  end
end

By inheriting from the Rails::Engine class, this gem notifies Rails that there's an engine at the specified path. Rails will automatically:

  • Add the engine’s app/ folder to the load path.
  • Load the engine’s models, mailers, controllers, and views.

The isolate_namespace Blorgh from the Engine class is crucial. It prevents your engine’s components (like models, routes, helpers, and controllers) from clashing with those in the host application or other engines.

For example:

  • A generated model becomes Blorgh::Article rather than Article.
  • The table name is blorgh_articles, not articles.
  • A controller becomes Blorgh::ArticlesController, with views in app/views/blorgh/articles.
  • A helper becomes Blorgh::ArticlesHelper.
  • Mailers and Jobs are namespaced as well.
  • Engine routes are kept isolated and don’t mix with the main app’s routes. This is discussed later in the Routes section of this guide.

It is recommended that the isolate_namespace line be left within the Engine class definition. Without this isolation, files from the engine might "leak" into the host app’s namespace or identically named classes might override each other.

2.3. Understanding the app Directory

The app directory in a mountable engine mirrors the familiar structure of a standard Rails application. It includes subdirectories like assets, controllers, helpers, jobs, mailers, models, and views.

Here’s what you should know about each of these, especially in the context of a namespaced engine like blorgh:

2.3.1. app/assets

Inside app/assets, you’ll find directories for images and stylesheets. Each of these contains a subdirectory named after your engine—blorgh in this case.

This namespacing is important because it ensures that your engine’s assets don’t conflict with those from other engines or the host application. For example:

app/assets/stylesheets/blorgh/application.css

2.3.2. app/controllers

The controllers folder contains a blorgh/ subdirectory where all engine controllers live. It starts with an application_controller.rb:

# app/controllers/blorgh/application_controller.rb
module Blorgh
  class ApplicationController < ActionController::Base
  end
end

This controller acts as the base for all controllers in the engine, much like ApplicationController does in a full app. Placing it (and all other controllers) in the blorgh/ module ensures they won’t clash with similarly named controllers in the host app or other engines.

2.3.3. Other Namespaced Directories

Similar to app/controllers, you’ll find a blorgh/ subdirectory under these other top-level folders:

  • app/helpers/blorgh/
  • app/jobs/blorgh/
  • app/mailers/blorgh/
  • app/models/blorgh/

Each of these may include a corresponding application_*.rb file (e.g., application_helper.rb) for defining shared behavior.

This consistent namespacing helps prevent naming collisions and keeps your engine modular and encapsulated.

2.3.4. app/views

Inside app/views/layouts, you’ll find a layout file for the engine:

app/views/layouts/blorgh/application.html.erb

This is the default layout used by views inside the engine. It’s useful if your engine is meant to be used as a self-contained application (e.g., admin dashboards, wikis, etc.). If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the application's app/views/layouts/application.html.erb file.

If you don't want to use a specific layout in the views of the engine, then you can delete this file and reference a different layout in the controllers of your engine.

2.3.5. /bin

This directory contains one file, bin/rails, which enables you to use the rails sub-commands and generators just like you would within an application. This means that you are able to generate new controllers and models for this engine very easily by running commands like this:

$ bin/rails generate model

Keep in mind, of course, that anything generated with these commands inside of an engine that has isolate_namespace in the Engine class will be namespaced.

2.3.6. /test

The test directory is where tests for the engine will go. To test the engine, there is a cut-down version of a Rails application embedded within it at test/dummy. This application will mount the engine at /blorgh, which will make it accessible through the application's routes at that path.

# test/dummy/config/routes.rb
Rails.application.routes.draw do
  mount Blorgh::Engine => "/blorgh"
end

Inside the test directory there is the test/integration directory, where integration tests for the engine should be placed. Other directories can be created in the test directory as well. For example, you may wish to create a test/models directory for your model tests.

3. Providing Engine Functionality

The engine that we'll build, blorgh, will allow you to add article submissions and comment functionality when added to any Rails application.

3.1. Generating an Article Resource

The first thing to generate for a blog engine is the Article model and related controller. To quickly generate this, you can use the Rails scaffold generator.

$ bin/rails generate scaffold article title:string text:text

This command will output this information:

invoke  active_record
create    db/migrate/<timestamp>_create_blorgh_articles.rb
create    app/models/blorgh/article.rb
invoke    test_unit
create      test/models/blorgh/article_test.rb
create      test/fixtures/blorgh/articles.yml
invoke  resource_route
 route    resources :articles
invoke  scaffold_controller
create    app/controllers/blorgh/articles_controller.rb
invoke    erb
create      app/views/blorgh/articles
create      app/views/blorgh/articles/index.html.erb
create      app/views/blorgh/articles/edit.html.erb
create      app/views/blorgh/articles/show.html.erb
create      app/views/blorgh/articles/new.html.erb
create      app/views/blorgh/articles/_form.html.erb
create      app/views/blorgh/articles/_article.html.erb
invoke    resource_route
invoke    test_unit
create      test/controllers/blorgh/articles_controller_test.rb
create      test/system/blorgh/articles_test.rb
invoke    helper
create      app/helpers/blorgh/articles_helper.rb
invoke      test_unit

3.1.1. The Migration and Model

The scaffold generator invokes the active_record generator, which generates a migration and a model for the resource.

The migration is named create_blorgh_articles instead of the usual create_articles, and the model is located at app/models/blorgh/article.rb rather than app/models/article.rb, since we used the isolate_namespace method in the Blorgh::Engine class.

3.1.2. The Test and Fixture

Next, the test_unit generator is invoked for this model, generating a model test at test/models/blorgh/article_test.rb (rather than test/models/article_test.rb) and a fixture at test/fixtures/blorgh/articles.yml (rather than test/fixtures/articles.yml).

3.1.3. The Route

Thereafter,resources :articles, for the article resource, is inserted into the config/routes.rb file for the engine.

Blorgh::Engine.routes.draw do
  resources :articles
end

The routes are created on the Blorgh::Engine object rather than the YourApp::Application class. This ensures that the engine routes are confined to the engine itself and can be mounted at a specific point, as shown in the Writing Tests section. It also allows the engine's routes to be isolated from routes that are within the application. The Routes section of this guide describes it in detail.

3.1.4. The Controller and Views

Next, the scaffold_controller generator is invoked, generating a controller called Blorgh::ArticlesController (at app/controllers/blorgh/articles_controller.rb) and its related views are created at app/views/blorgh/articles.

This generator also generates tests for the controller (test/controllers/blorgh/articles_controller_test.rb and test/system/blorgh/articles_test.rb) and a helper (app/helpers/blorgh/articles_helper.rb).

Once again, everything is namespaced. The controller's class is defined within the Blorgh module:

module Blorgh
  class ArticlesController < ApplicationController
    # ...
  end
end

The ArticlesController class inherits from Blorgh::ApplicationController, not the application's ApplicationController.

3.1.5. The Helper

The helper inside app/helpers/blorgh/articles_helper.rb is also namespaced:

module Blorgh
  module ArticlesHelper
    # ...
  end
end

This prevents conflicts with any other engine or application that may have an article resource as well.

3.1.6. Exploring the Engine in the Browser

You can explore the engine by first running bin/rails db:migrate at the root of the engine. Then run bin/rails server in the engine's root directory to start the server.

When you open http://localhost:3000/blorgh/articles you will see the default scaffold that has been generated.

Congratulations, you've just generated your first engine!

3.1.7. Exploring the Engine in the Console

If you'd rather explore in the console, you can run bin/rails console in the root directory of the engine. Remember: the Article model is namespaced, so to reference it you must call it as Blorgh::Article.

irb> Blorgh::Article.create(title: "Hello, world!", text: "This is a test article.")
=>  #<Blorgh::Article id: 1, title: "Hello, world!", text: "This is a test article.", created_at: "2025-07-13 07:55:27.610591000 +0000", updated_at: "2025-07-13 07:55:27.610591000 +0000">

irb> Blorgh::Article.find(1)
=> #<Blorgh::Article id: 1, title: "Hello, world!" ...>

Whenever someone goes to the root path where the engine is mounted, they should be shown a list of articles. To do this, add the following line

root to: "articles#index"

to the config/routes.rb file inside the engine.

Now you will only need to go to the root of the engine to see all the articles, rather than visiting /articles. This means that instead of visiting http://localhost:3000/blorgh/articles, you can now go to http://localhost:3000/blorgh.

3.2. Generating a Comments Resource

Now that the engine can create new articles, add the ability to comment. To do this, you'll need to generate a comment model, a comment controller, and then modify the articles scaffold to display comments and allow people to create new ones.

From the engine root, run the model generator to generate a Comment model, with the related table having two columns: an article references column and a text text column.

$ bin/rails generate model Comment article:references text:text

This will output the following:

invoke  active_record
create    db/migrate/<timestamp>_create_blorgh_comments.rb
create    app/models/blorgh/comment.rb
invoke    test_unit
create      test/models/blorgh/comment_test.rb
create      test/fixtures/blorgh/comments.yml

This will generate a Blorgh::Comment model and a migration to create the blorgh_comments table.

The generated migration will look like this:

# db/migrate/<timestamp>_create_blorgh_comments.rb
class CreateBlorghComments < ActiveRecord::Migration[8.0]
  def change
    create_table :blorgh_comments do |t|
      t.references :article, null: false, foreign_key: true
      t.text :text

      t.timestamps
    end
  end
end

However, since you're building an isolated engine, the model Blorgh::Comment references Blorgh::Article, so the article_id foreign key should point to the blorgh_articles table, not an articles table.

To fix this, you can modify the existing migration file to look like this:

# db/migrate/<timestamp>_create_blorgh_comments.rb
class CreateBlorghComments < ActiveRecord::Migration[8.0]
  def change
    create_table :blorgh_comments do |t|
      t.references :article, null: false, foreign_key: { to_table: :blorgh_articles }
      t.text :text

      t.timestamps
    end
  end
end

Now, you can run the migration to create the blorgh_comments table:

$ bin/rails db:migrate

3.2.1. Updating the View to Show the Comments

To show the comments on an article, edit app/views/blorgh/articles/show.html.erb and add this line before the "Edit" link:

<h3>Comments</h3>
<%= render @article.comments %>

To get the comments to display on an article, define a has_many association on the Blorgh::Article model:

# app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    has_many :comments
  end
end

Since the has_many association is defined inside a class that is inside the Blorgh module, Rails knows that you want to use the Blorgh::Comment model for these objects, so there's no need to specify the :class_name option.

3.2.2. Adding a Form and Resource Route to Create Comments

Next, create a form so that comments can be added to an article. Render the blorgh/comments/form partial in app/views/blorgh/articles/show.html.erb underneath the render @article.comments line.

...
  <%= render @article.comments %>

  <!-- Render the comments form -->
  <%= render "blorgh/comments/form" %>
...

Next, create the blorgh/comments/form partial that we just referenced. To do this, create a new directory at app/views/blorgh/comments and in it a new file called _form.html.erb with the following content:

<h3>New comment</h3>
<%= form_with model: [@article, @article.comments.build] do |form| %>
  <p>
    <%= form.label :text %><br>
    <%= form.textarea :text %>
  </p>
  <%= form.submit %>
<% end %>

When this form is submitted, it will attempt to perform a POST request to the /articles/:article_id/comments route within the engine. You can read more about the form_with helper in the guides.

However, this route doesn't exist yet. You can create it by nesting the comments resource inside the articles resource in config/routes.rb:

resources :articles do
  resources :comments
end

Now, create the controller that will handle the POST request to the /articles/:article_id/comments route:

$ bin/rails generate controller comments

This will generate the following things:

create  app/controllers/blorgh/comments_controller.rb
invoke  erb
 exist    app/views/blorgh/comments
invoke  test_unit
create    test/controllers/blorgh/comments_controller_test.rb
invoke  helper
create    app/helpers/blorgh/comments_helper.rb
invoke    test_unit

As mentioned, the form will make a POST request to /articles/:article_id/comments, so we'll need a create action in Blorgh::CommentsController:

# app/controllers/blorgh/comments_controller.rb
def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.create(comment_params)
  flash[:notice] = "Comment has been created!"
  redirect_to articles_path
end

private
  def comment_params
    params.expect(comment: [:text])
  end

However, if you were to create a comment, you would see an error where the engine is unable to find the partial required for rendering the comments:

Missing partial blorgh/comments/_comment with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby]}.

Rails will first look in the application's (test/dummy) app/views directory and then in the engine's app/views directory. When it can't find the file, it will throw this error. The engine looks for blorgh/comments/_comment because the comment object it's rendering is an instance of the Blorgh::Comment class.

Create a new file at app/views/blorgh/comments/_comment.html.erb and add the following line to display the comment text:

<%= comment_counter + 1 %>. <%= comment.text %>

The comment_counter local variable is given to us by the <%= render @article.comments %> call, which will define it automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created.

That completes the comment functionality of the blogging engine. Now it's time to use it within an application.

4. Using the Engine in a Host Application

This section explains how to mount the engine into an application, perform the initial setup, and connect the engine to an existing class defined by the host application.

4.1. Mounting the Engine

To use the engine in a host application, add it to the application's Gemfile. If you don't already have an application to test with, you can generate one outside the engine directory using the rails new command:

$ cd .. # Exit the engine folder
$ rails new host_application

This means the host application and the engine will live side by side in your filesystem, like this:

/your_project_root/
├── blorgh/           # The engine
└── host_application/ # The app where you test the engine

In a production application, once your gem has been published, you would add the engine to your Gemfile, like you do with other gems. For example, to add Devise to your application, you would add the following line to your Gemfile:

gem "devise"

However, since the engine is not published as a gem yet, and you're developing it locally, you need to link to it using a relative path in the host application's Gemfile:

gem "blorgh", path: "../blorgh"

Now that the local path to the gem is specified, run bundle install to install it.

By including the engine in the application's Gemfile, it will be loaded automatically when Rails boots. Rails will first require the engine’s entry point file at lib/blorgh.rb. This file typically sets up the namespace and requires additional files, including lib/blorgh/engine.rb, which defines the Blorgh::Engine class. This Engine class is responsible for hooking into the Rails application and mounting the engine's initializers, and other configurations.

To make the engine's functionality available within a host application, you need to mount it in the application's config/routes.rb file:

mount Blorgh::Engine, at: "/blog"

This mounts the engine at the /blog path, making its routes accessible at http://localhost:3000/blog when the application is running.

Some engines, like Devise, expose custom routing helpers (such as devise_for) instead of using mount. These helpers internally mount parts of the engine's functionality at specific paths and provide additional configuration options tailored to the engine's domain.

However, if you try to run the application at this point, you will see an error like this:

SQLite3::SQLException: no such table: blorgh_articles:
SELECT "blorgh_articles".* FROM "blorgh_articles" /*action='index',application='HostApplication',controller='articles'*/

This is because the engine's migrations haven't been copied over to the host application's database yet. The next section explains how to do this.

4.2. Engine Setup

The engine contains migrations for the blorgh_articles and blorgh_comments tables which need to be created in the application's database so that the engine's models can query them correctly.

4.2.1. Copying the Migrations

To copy these migrations into the application run the following command from the application's root:

$ bin/rails blorgh:install:migrations

which will output something like this:

Copied migration <timestamp_1>_create_blorgh_articles.blorgh.rb from blorgh
Copied migration <timestamp_2>_create_blorgh_comments.blorgh.rb from blorgh

When run for the first time, bin/rails blorgh:install:migrations copies over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. This is useful if you want to revert the migrations from the engine.

4.2.2. Migrations for Multiple Engines

If you have multiple engines referenced in the host application, that need migrations copied over, use railties:install:migrations instead:

$ bin/rails railties:install:migrations

This will save you from having to run a separate install:migrations task for each engine individually.

4.2.3. Referencing a Custom Path for the Migrations

If your engine stores its migrations in the non-default location, you can specify a custom path in the source engine for the migrations using MIGRATIONS_PATH:

$ bin/rails railties:install:migrations MIGRATIONS_PATH=db_blorgh

4.2.4. Migrations for Multiple Databases

If you have multiple databases within an engine, you can specify the target database by specifying the DATABASE option:

$ bin/rails railties:install:migrations DATABASE=animals

These tasks are provided by ActiveRecord::Railtie, the Rails component responsible for managing how Active Record integrates into an application. When run, it looks through all loaded engines and copies any exposed migrations into the host application's db/migrate folder, saving you from having to run a separate install:migrations task for each engine individually.

4.2.5. Running Migrations

To run these migrations within the context of the application, run:

$ bin/rails db:migrate

4.2.6. Running and Reverting Migrations for Only One Engine

If you have multiple engines referenced in the host application, and you would like to run migrations only from one engine, you can do it by specifying SCOPE:

$ bin/rails db:migrate SCOPE=blorgh

This scope may also be useful if you want to revert an engine's migrations before removing it.

To revert all migrations from blorgh engine you can run code such as:

$ bin/rails db:migrate SCOPE=blorgh VERSION=0

Once you've run the migrations, you can access the engine through http://localhost:3000/blog.

The articles will be empty, because the table created inside the application is different from the one created within the engine. You can explore the engine through the host application, in the same way as you did when it was only an engine.

4.3. Connecting Engine Records to Application Models

When building a Rails engine, you may want to connect the engine's records to models defined by the host application. For example, in the blorgh engine, you might want each article or comment to have an associated author. While the engine sets up the author relationship, the actual model, User in this case, comes from the host application.

This section explains how to associate an Article from the engine with a User from the host application. For simplicity, assume the host application uses a model called User, but there could be a case where a different host application calls this class something different, such as Person. For this reason, the engine should not hardcode associations specifically for a User class. Instead, it should be configurable. This is covered in the next section.

4.3.1. Generating a User Model in the Host Application

To keep it simple in this case, the application will have a class called User that represents the users of the application. It can be generated using this command inside the application's root directory:

$ bin/rails generate model user name:string

The bin/rails db:migrate command needs to be run here to ensure that our application has the users table for future use.

4.3.2. Associating author_name from the Engine to a Class in the Host Application

The article form will include a new text field called author_name, where users can enter their name. The engine will use this name to either find an existing User object or create a new one. It will then associate the article with that User (or whatever class the host application uses to represent authors) as the article's author.

Add the author_name text field to the app/views/blorgh/articles/_form.html.erb partial inside the engine. This can be added above the title field with the following code:

<%= form_with(model: article) do |form| %>
  <%# ... %>

  <div class="field">
    <%= form.label :author_name %><br>
    <%= form.text_field :author_name %>
  </div>

  <div>
    <%= form.label :title, style: "display: block" %>
    <%= form.text_field :title %>
  </div>
  <%# ... %>
<% end %>

The author's name should also be displayed on the article's page. Add this code above the "Title" output inside app/views/blorgh/articles/_article.html.erb:

<p>
  <strong>Author:</strong>
  <%= article.author.name %>
</p>

Next, we need to update the Blorgh::ArticlesController#article_params method to permit the new form parameter:

def article_params
  params.expect(article: [:title, :text, :author_name])
end

The Blorgh::Article model should include logic to convert the author_name field into an actual User object (or whatever class the host application uses for authors) and associate it with the article before it is saved. It should also define an attr_accessor for author_name to provide getter and setter methods for this attribute.

Start by adding the attr_accessor for author_name, the association for the author and the before_validation call into app/models/blorgh/article.rb.

# app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    has_many :comments

    # Add the author association to the model
    attr_accessor :author_name
    belongs_to :author, class_name: "User" # The User class exists in the host application

    before_validation :set_author

    private
      def set_author
        self.author = User.find_or_create_by(name: author_name)
      end
  end
end

For now, this setup allows the engine to associate an author name with the User model defined by the host application, even though it introduces a coupling that will be removed later. It will be made more configurable in the next section.

There also needs to be a way of associating the records in the blorgh_articles table with the records in the users table. Because the association in the engine is called author, there should be an author_id column added to the blorgh_articles table.

To generate this new column, run this command within the engine's root directory:

$ bin/rails generate migration add_author_id_to_blorgh_articles author_id:integer

4.3.3. Copying and Running the Migration in the Host Application

As discussed in the Copying the Migrations section, this new migration will need to be copied to the host application:

$ bin/rails blorgh:install:migrations
Copied migration <timestamp>_add_author_id_to_blorgh_articles.blorgh.rb from blorgh

Notice that only the latest migration was copied over. This is because the first two migrations were copied over the first time this command was run in a previous section.

Run the migration using:

$ bin/rails db:migrate

4.3.4. Viewing the Association in the Rails console

Now that you've associated the author_name from the engine to the User model in the host application, you can go to the form in the host application at http://localhost:3000/blog/articles/new and create an article with an author name that will create and link to a User record in the host application.

If you open up the rails console in the host application, you can view the Blorgh::Article record that was created, and see that it is associated with the User record from the host application:

irb> article = Blorgh::Article.last
=> #<Blorgh::Article id: 1, title: "Hello, World!", text: "This is a test article.", created_at: "2025-07-16 19:04:54.552457000 +0000", updated_at: "2025-07-16 19:04:54.552457000 +0000", author_id: 1>

irb> user = article.author
=> #<User id: 1, name: "Fake Author 1", created_at: "2025-07-16 19:04:54.542709000 +0000", updated_at: "2025-07-16 19:04:54.542709000 +0000">

4.3.5. Using a Controller Provided by the Application

In a typical Rails application, all controllers inherit from ApplicationController, which often contains shared logic like authentication methods or session helpers.

Rails engines, however, are isolated by default. Each engine has its own ApplicationController (like Blorgh::ApplicationController) to avoid conflicts with the main app. However, sometimes, the engine's controllers need to access methods defined in the main application's ApplicationController. This is often necessary for features like authentication (current_user), authorization checks, or helper methods that manage things like user preferences, flash messages, or layout logic. This is shared functionality that's already defined in the main application and shouldn't be re-implemented in the engine.

To make this possible, you can update the engine’s ApplicationController so that it inherits from the main app’s ApplicationController instead of being isolated. In the Blorgh engine, you would change the file app/controllers/blorgh/application_controller.rb to:

# app/controllers/blorgh/application_controller.rb
module Blorgh
  class ApplicationController < ::ApplicationController
  end
end

The ::ApplicationController here refers to the main application's controller. With this change, all controllers in the engine (which inherit from Blorgh::ApplicationController by default) will now also have access to methods from the main app’s controller, like current_user, authentication helpers, or anything else defined there.

Keep in mind that this setup only works when the engine is being used inside a host application that has its own ApplicationController.

4.4. Configuring the Engine to Use a Custom Class

Earlier, we hard-coded the author association to the User class. However, this approach isn't ideal, because the engine shouldn't assume the existence of a specific class in the host application. For example, some applications might use a differently named class, such as Person, to represent authors.

In this section we'll make the class that represents a User in the application customizable for the engine, allowing the engine to work with any model the host application chooses to use. This will be followed by general configuration tips for the engine.

4.4.1. Creating the author_class_name Configuration Setting in the Engine

To make the class that represents a User in the application customizable for the engine, the engine should have a configuration setting called author_class_name that will be used to specify which class name represents authors within the host application.

To define this configuration setting, you should use a mattr_accessor inside the Blorgh module for the engine. Add this line to the Blorgh module:

# lib/blorgh.rb
module Blorgh
  mattr_accessor :author_class_name
end

This method provides a getter and setter method on the module with the specified name. To use it, it must be referenced using Blorgh.author_class_name.

The next step is to switch the Blorgh::Article model over to this new setting. Replace belongs_to :author, class_name: "User" with Blorgh.author_class_name in the Blorgh::Article model:

# app/models/blorgh/article.rb
belongs_to :author, class_name: Blorgh.author_class_name

The line self.author = User.find_or_create_by(name: author_name) in the Blorgh::Article model should also use this class name instead of User:

# app/models/blorgh/article.rb

def set_author
  self.author = Blorgh.author_class_name.constantize.find_or_create_by(name: author_name)
end

To save having to call constantize on the author_class_name result all the time, you could create a convenience helper to constantize on demand when you need the Class.

# lib/blorgh.rb
module Blorgh
  mattr_accessor :author_class_name

  def self.author_class
    author_class_name.constantize
  end
end

This would then turn the above code for set_author into this:

# app/models/blorgh/article.rb
self.author = Blorgh.author_class.find_or_create_by(name: author_name)

4.4.2. Setting the author_class_name Configuration Setting in the Host Application

To set this configuration setting within the host application, an initializer can be used. By using an initializer, the configuration will be set up before the application starts and before it calls the engine's models. This is important because the engine's models may depend on this configuration setting existing.

Create a new initializer at config/initializers/blorgh.rb inside the host application and set the author_class_name configuration setting to User:

Blorgh.author_class_name = "User"

In a different host application where the model is called Person, you would set the author_class_name configuration setting to Person.

Be sure to pass the class name as a string (e.g., "User"), not as a constant (User). If you use the class directly, Rails may try to load it and its associated table before the application has fully initialized. This can cause errors if the table hasn't been created yet. By using a string, the engine can safely convert it to a class later using constantize, after initialization is complete.

Try creating a new article - everything should work just as before. The key difference is that the engine now uses the author_class_name configuration set in config/initializers/blorgh.rb to determine which model to associate as the author.

At this point, the engine no longer has a hardcoded dependency on a specific class name like User. Instead, it relies on whatever class name is specified in the configuration. The only requirement is that the configured class name is a class that responds to find_or_create_by and returns an object that can be associated with an article. That object should also have an identifiable attribute (such as an id) that can be used for lookup and display.

4.4.3. General Engine Configuration

Within an engine, there may come a time where you wish to use things such as initializers, internationalization, or other configuration options. The great news is that these things are entirely possible, because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines!

If you wish to use an initializer - code that should run before the engine is loaded - the place for it is the config/initializers folder. This directory's functionality is explained in the Initializers section of the Configuring Rails Applications guide, and works precisely the same way as the config/initializers directory inside an application. The same thing goes if you want to use a standard initializer.

For locales, simply place the locale files in the config/locales directory, just like you would in an application.

5. Improving the Engine

This section explains how to add and/or override engine functionality in the main Rails application.

5.1. Overriding Models and Controllers

Engine models and controllers can be reopened and extended by the host application to customize or override their behavior. This is useful when the host application needs to make changes, such as adding validations to a model or adjusting controller logic, without modifying the engine’s source code directly.

A common approach is to place override files in a dedicated directory, such as app/overrides, and manually load them during application initialization. This directory is ignored by the Rails autoloader to prevent naming conflicts and to prevent unintended constant loading.

Here’s how you can set this up in the host application:

# config/application.rb
module HostApplication
  class Application < Rails::Application
    # ...

    overrides = Rails.root.join("app/overrides")
    Rails.autoloaders.main.ignore(overrides)

    config.to_prepare do
      Dir.glob("#{overrides}/**/*_override.rb").sort.each do |override|
        load override
      end
    end
  end
end

5.1.1. Why Use to_prepare and load

The to_prepare block ensures that overrides are reloaded on each request in development (and only once in production), which is helpful when working with engines during development. Using load instead of require allows the override files to be reloaded without restarting the server.

5.1.2. Why Ignore Autoloading

The app/overrides directory is ignored by Zeitwerk (Rails’ autoloader) so that you can control exactly when the files are loaded. This prevents potential conflicts with similarly named classes or modules elsewhere in the application.

5.1.3. Naming Convention

It’s recommended to suffix override files with _override.rb (e.g., article_override.rb) to clearly distinguish them from standard models and controllers and to avoid any conflicts with autoloaded files.

5.1.4. Reopening Existing Classes Using class_eval

In order to override the engine model

# blorgh/app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    # ...
  end
end

you can create a file that reopens that class. This example reopens the Blorgh::Article model and adds a validation for the title attribute.

# host_application/app/overrides/models/blorgh/article_override.rb
Blorgh::Article.class_eval do
  validates :title, presence: true, length: { minimum: 10 }
end

It is very important that the override reopens the class or module. Using the class or module keywords would define them if they were not already in memory, which would be incorrect because the definition lives in the engine. Using class_eval as shown above ensures you are reopening an existing module.

5.1.5. Reopening Existing Classes Using ActiveSupport::Concern

While Class#class_eval is useful for making simple runtime changes to a class, more complex modifications—especially those involving multiple modules with dependencies—are often better handled using ActiveSupport::Concern. It provides a structured way to define module behavior and dependencies, ensuring consistent load order and making it easier to organize and compose reusable code.

Suppose you want to extend the Blorgh::Article model from the host application by:

  • Adding a new instance method: time_since_created
  • Overriding the existing summary method

Here’s how you can do it cleanly using ActiveSupport::Concern:

5.1.5.1. Defining a Concern

First, define a concern that contains the shared behavior:

# blorgh/lib/concerns/models/article.rb
module Blorgh::Concerns::Models::Article
  extend ActiveSupport::Concern

  included do
    attr_accessor :author_name
    belongs_to :author, class_name: "User"

    before_validation :set_author

    private
      def set_author
        self.author = User.find_or_create_by(name: author_name)
      end
  end

  def summary
    "#{title}"
  end

  module ClassMethods
    def some_class_method
      "some class method string"
    end
  end
end
5.1.5.2. Setting Up the Engine’s Base Model

Next, include the concern in the engine’s Article model. This ensures that engine users who don’t override the model still benefit from the concern’s behavior:

# blorgh/app/models/blorgh/article.rb
module Blorgh
  class Article < ApplicationRecord
    include Blorgh::Concerns::Models::Article
  end
end
5.1.5.3. Extending in the Host Application

Finally, the host application can override and extend the model by reopening it. Here we add a new instance method and override the existing summary method:

# host_application/app/models/blorgh/article.rb
class Blorgh::Article < ApplicationRecord
  include Blorgh::Concerns::Models::Article

  def time_since_created
    Time.current - created_at
  end

  def summary
    "#{title} - #{truncate(text)}"
  end
end

5.2. Autoloading and Engines

Please check the Autoloading and Reloading Constants guide for more information about autoloading and engines.

5.3. Overriding Views

When the host application looks for a view to render, it will first look in the app/views directory of the application. If it cannot find the view there, it will then check in the app/views directories of all engines that have this directory.

For example, when the host application is asked to render the view for Blorgh::ArticlesController's index action, it will first look for the path app/views/blorgh/articles/index.html.erb within the application. If it cannot find it, it will then look inside the engine.

You can override this view in the host application by creating a new file at app/views/blorgh/articles/index.html.erb. Then you can completely change what this view would normally output, like the following:

<h1>Articles</h1>
<%= link_to "New Article", new_article_path %>
<% @articles.each do |article| %>
  <h2><%= article.title %></h2>
  <small>By <%= article.author.name %></small>
  <%= simple_format(article.text) %>
  <hr>
<% end %>

The new view at localhost:3000/blog/articles will now display the updated view with the new content.

5.4. Routes

Routes defined inside a Rails engine are isolated from the main application by default. This isolation is established by the isolate_namespace call in the engine’s Engine class. It allows both the engine and the application to define routes with the same names—like articles_path—without conflict.

5.4.1. Defining Engine Routes

You can define routes inside the engine using the engine's route set:

# blorgh/config/routes.rb
Blorgh::Engine.routes.draw do
  resources :articles
end

When your engine is mounted into a host application (e.g., at /blog), this creates routes like:

  • /blog/articlesBlorgh::ArticlesController#index

5.4.2. Linking to Engine Routes from the Application

Because routes are namespaced and isolated, route helpers like articles_path may refer to either the engine's route or the application's route, depending on where the view is rendered from.

For example:

<%= link_to "Blog articles", articles_path %>

If rendered from the application, it will likely resolve to the application's articles_path. If rendered from inside the engine, it may resolve to the engine's articles_path.

To ensure you're linking to the engine's route, use the engine's named routing proxy. For the blorgh engine, that would be:

<%= link_to "Blog articles", blorgh.articles_path %>

This explicitly tells Rails to use the articles_path defined in the Blorgh engine.

The name of the proxy method (blorgh) matches the name of the engine as declared in engine.rb via isolate_namespace Blorgh.

5.4.3. Linking to Application Routes from the Engine

If you need to reference a route defined in the main application from within the engine (for example, linking to the homepage), you can use the main_app routing proxy:

<%= link_to "Home", main_app.root_path %>

This ensures the route always points to the host application, even when called from within engine views.

5.4.4. Targeted Routes

If you call a route helper like root_path from inside an engine view, and both the engine and application define a root route, Rails may not know which one to use. Or worse, it may raise an error if the route doesn't exist in the engine.

To prevent this, use main_app.root_path to target the application, and set blorgh.root_path (or your engine's namespace) to target the engine.

Being explicit with routing proxies ensures that your routes behave consistently and avoids surprising bugs when working across engines and applications.

5.5. Assets

Assets in a Rails engine work just like they do in a full Rails application. Because your engine inherits from Rails::Engine, Rails will automatically look for assets in the engine’s app/assets and lib/assets directories.

To avoid naming conflicts with the host application, all engine assets should be namespaced under a subdirectory that matches the engine’s name. For example, instead of placing a stylesheet directly at app/assets/stylesheets/style.css, you should place it at app/assets/stylesheets/<engine_name>/style.css, which would be app/assets/stylesheets/blorgh/style.css for the blorgh engine.

This prevents collisions with assets in the host application that may have the same filename.

To include a namespaced engine asset in the host application, reference it using its full path with the stylesheet_link_tag (or similar helpers):

<%= stylesheet_link_tag "blorgh/style.css" %>

This will correctly load the stylesheet located at app/assets/stylesheets/blorgh/style.css within the engine.

If you’re using the Asset Pipeline (Sprockets), you can also include engine assets as dependencies within other stylesheets using a require directive:

/*
 *= require blorgh/style
 */

This allows engine styles to be bundled into application-wide stylesheets.

Remember that in order to use languages like Sass or Haml, you should add the relevant library to your engine's .gemspec.

5.6. Separate Assets and Precompiling

In some cases, your engine may include assets that are not required by the host application. For example, suppose your engine provides an admin interface with its own layout and styles. These assets, such as admin.css or admin.js, are only used within the engine and don't need to be included in the host application's asset pipeline.

In such situations, it doesn’t make sense for the host app to reference these assets manually (e.g., via stylesheet_link_tag "blorgh/admin"). Instead, you should explicitly tell Rails to precompile these assets so they’re available when the engine is used.

To do this, you can add a precompilation hook in your engine's engine.rb file:

# lib/blorgh/engine.rb
module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh

    initializer "blorgh.assets.precompile" do |app|
      app.config.assets.precompile += %w( blorgh/admin.js blorgh/admin.css )
    end
  end
end

This ensures that these engine-specific assets will be compiled when running:

bin/rails assets:precompile

Be sure to include the full namespaced paths (e.g. blorgh/admin.css) so Sprockets can locate the correct files within your engine.

For more information, read the Asset Pipeline guide.

5.7. Writing Tests

The test/ directory works just like it does in a standard Rails application. You can write unit tests, functional tests, and integration tests to ensure your engine behaves as expected.

When a Rails engine is generated, it includes a minimal host application inside the test/dummy directory. This "dummy app" is used solely for development and testing—it simulates how your engine will behave when used in a real application. You can extend the dummy app by adding controllers, models, or views as needed to help you test the engine’s functionality in context.

5.7.1. Functional and Integration Tests

Since engines are not full applications, they need to be mounted into a host in order to test things like routing and controllers. That’s where the dummy app comes in.

When writing controller or integration tests, your tests need to be aware of the engine’s routing context. Rails doesn’t automatically assume you’re using the engine’s routes, so if you write a test like this:

# test/controllers/blorgh/articles_controller_test.rb
module Blorgh
  class ArticlesControllerTest < ActionDispatch::IntegrationTest
    test "can get index" do
      get articles_path
      assert_response :success
    end
  end
end

It might fail because articles_path could resolve to the dummy app (or not at all), rather than your engine. To fix this, you must tell Rails explicitly to use the engine's routes in your test setup:

# test/controllers/blorgh/articles_controller_test.rb
module Blorgh
  class ArticlesControllerTest < ActionDispatch::IntegrationTest
    setup do
      @routes = Engine.routes
    end

    test "can get index" do
      get articles_path
      assert_response :success
    end
  end
end

This ensures the test uses the routing context defined in Blorgh::Engine.routes, and that the path helpers like articles_path map correctly to /blog/articles (since your engine is mounted at /blog).

Even though the engine is mounted at /blog in the host app, you don't need to include that prefix in test paths. Setting @routes = Engine.routes scopes everything correctly for you.

The following test checks that the index page for articles loads successfully and renders the expected heading.

# test/controllers/blorgh/articles_controller_test.rb
require "test_helper"

module Blorgh
  class ArticlesControllerTest < ActionDispatch::IntegrationTest
    setup do
      @routes = Engine.routes
    end

    test "can get index" do
      get articles_path
      assert_response :success
      assert_select "h1", "Articles"
    end
  end
end

5.7.2. Unit and Model Tests

You can test your engine’s models just like in a regular Rails app:

# test/models/blorgh/article_test.rb
require "test_helper"

module Blorgh
  class ArticleTest < ActiveSupport::TestCase
    test "title is required" do
      article = Article.new(text: "Some content")
      assert_not article.valid?
      assert_includes article.errors[:title], "can't be blank"
    end
  end
end

5.8. Gem Dependencies

Gem dependencies inside an engine should be specified inside the .gemspec file at the root of the engine to allow the engine to be installed as a gem.

If the dependencies were to be specified inside the Gemfile, instead of the .gemspec file, these would not be recognized by a traditional gem install and so they would not be installed, causing the engine to malfunction.

To specify a dependency that should be installed with the engine during a gem install, specify it inside the Gem::Specification block inside the .gemspec file in the engine:

s.add_dependency "moo"

To specify a dependency that should only be installed as a development dependency of the application, specify it like this:

s.add_development_dependency "moo"

Both kinds of dependencies will be installed when bundle install is run inside of the application. The development dependencies for the gem will only be used when the development and tests for the engine are running.

If you want to immediately require dependencies when the engine is required, you should require them before the engine's initialization. For example, in the engine.rb file :

require "other_engine/engine"
require "yet_another_engine/engine"

module MyEngine
  class Engine < ::Rails::Engine
  end
end


Back to top