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:
- Devise which provides authentication for its parent applications
- Thredded which provides forum functionality
- Refinery CMS which provides a CMS engine
- Active Storage which provides file storage as an engine.
- Action Text which provides a rich text editor as an engine.
Rails::Applicationinheriting much of its behavior fromRails::Engine.
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
Blorghmodule, 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
appdirectory tree A
config/routes.rbfile:Rails.application.routes.draw do endA file at
lib/blorgh/engine.rb, which is identical in function to a standard Rails application'sconfig/application.rbfile:# 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
ApplicationControllerstub - A namespaced
ApplicationHelperstub - A layout view template for the engine
Namespace isolation to
config/routes.rb:Blorgh::Engine.routes.draw do endNamespace isolation to
lib/blorgh/engine.rbas 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::Articlerather thanArticle. - The table name is
blorgh_articles, notarticles. - A controller becomes
Blorgh::ArticlesController, with views inapp/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
summarymethod
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/articles→Blorgh::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