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