Rails engines represent one of the most powerful yet underutilized features of the Ruby on Rails framework. Since Rails 3, developers have been able to write engines in a clean, modular style that can be packaged as RubyGems--prepackaged applications that integrate seamlessly into host Rails applications. Whether you're building a blog feature that needs to span multiple projects, creating reusable authentication components, or architecting a modular SaaS platform, engines provide the foundation for code reuse and modular architecture that modern Rails applications increasingly demand.
This comprehensive guide walks through everything you need to know to create your own Rails engine gem, from understanding the fundamental differences between engines, plugins, and gems, through building and testing a complete engine, to integrating it into host applications and configuring it for maximum flexibility. Our web development team specializes in Rails architecture and can help you implement engine-based solutions for complex applications.
Understanding Rails Engines: The Foundation
What Makes an Engine
A Rails engine is essentially a miniature Rails application that provides functionality to a host application. This is a concept worth internalizing: Rails applications themselves are just "supercharged" engines, with the Rails::Application class inheriting much of its behavior from Rails::Engine. The implications of this architecture are profound--you can extract any feature from a Rails application, wrap it in an engine, and reuse it across multiple projects.
Engines and applications share a common structure, including app, config, lib, and other directories. An engine can have its own models, views, controllers, generators, migrations, and even publicly served static files. This means you're not limited to adding simple helpers or utility methods--you can build entire feature domains that integrate as cohesive units into host applications.
The key insight is that engines promote a modular architecture without requiring the complexity of microservices. Instead of managing multiple services that communicate over HTTP or message queues, you can maintain multiple engines within a single Rails application (or across multiple applications) that share the same process space and database connections while remaining cleanly separated. This approach is particularly valuable for organizations building scalable SaaS platforms that require clear boundaries between feature modules.
Rails Engines vs Plugins vs Gems
Ruby gems are packaged code shared across Ruby projects. They are Ruby's equivalent to Node's NPM packages. Gems can contain anything from simple utility functions to complex libraries. The Rails framework itself is distributed as a gem.
Rails plugins extend the functionality of Rails applications but in a more limited way than engines. Plugins have been the precursor to engines in the Rails ecosystem and share a similar lib directory structure. The rails plugin new generator creates both plugins and engines--the distinction is in how they're configured.
Rails engines are miniature Rails applications that interface with your main application. An engine can have its own models, views, controllers, routes, and assets. While most Rails engines are distributed as Ruby gems, not every Ruby gem is a Rails engine. The key differentiator is that engines are designed specifically for Rails applications and can include Rails-specific components.
The relationship can be summarized as: All engines are plugins, but not all plugins are engines. An engine is essentially a "full plugin" with all the features needed to function as a complete, mountable Rails application.
Engines excel in these common scenarios
Sharing Across Applications
Extract and reuse functionality like authentication, billing, or reporting across multiple Rails projects.
Modular Large Applications
Break down monolithic applications into manageable, testable components with clear boundaries.
Team Collaboration
Enable different teams to work on separate features independently while maintaining cohesion.
Types of Rails Engines
Mountable Rails Engines
Mountable Rails engines are designed to be isolated from the main Rails application using an entirely separate namespace. They operate as self-contained units within your main application. Models, routes, and assets are namespaced to the engine, preventing conflicts with the host Rails application.
With mountable engines, you must explicitly mount the routes in the host Rails application's routes.rb file:
Rails.application.routes.draw do
mount ExampleEngine::Engine => "/example"
end
The isolation provided by mountable engines comes from the isolate_namespace directive in the engine definition. This single line ensures that all controllers, models, routes, and other components are wrapped in the engine's namespace. A model generated within a mountable engine called "blorgh" would be Blorgh::Article rather than Article, and it would use the blorgh_articles table rather than articles.
Mountable engines are the right choice for self-contained features built into gems that benefit from isolation. They prevent naming collisions and allow multiple engines (or the host application) to have routes, models, or controllers with the same names without conflicts.
Non-Mountable Rails Engines
Non-mountable Rails engines (sometimes called "full" engines) are less isolated than mountable engines. They share a namespace with the host Rails application and don't require manual mounting of routes. Instead, they blend more seamlessly with the main Rails application.
Non-mountable engines are more common than you might realize. They're particularly useful when you want to extend core functionality of your Rails application in a deeply integrated way--adding new model concerns, view helpers, or business logic that needs to be shared across multiple models or controllers. The lack of isolation reduces overhead and allows the engine's components to feel like natural parts of the host application.
The trade-off is that non-mountable engines can lead to naming conflicts if not carefully managed. They're best suited for internal use within a single organization or application, rather than as gems distributed to other projects.
Choosing the Right Type
Choose mountable when building a gem that will be distributed to other projects, when the engine represents a discrete feature with clear boundaries, when you want to prevent naming conflicts with the host application, or when multiple engines might coexist in the same application.
Choose non-mountable when building internal shared code within your organization, when deep integration with the host application is required, when the engine extends core Rails functionality rather than adding discrete features, or when simplicity and reduced overhead are priorities.
| Characteristic | Mountable | Non-Mountable |
|---|---|---|
| Namespace Isolation | Full isolation with isolate_namespace | Shared namespace with host application |
| Route Mounting | Explicit mount required in routes.rb | Automatic integration |
| Naming Conflicts | Prevented by namespacing | Possible without careful management |
| Best For | Distributed gems, multi-project reuse | Internal sharing within organization |
| Complexity | Higher (more isolation overhead) | Lower (simpler integration) |
Generating Your First Engine
Using the Rails Plugin Generator
Rails provides a built-in generator for creating new engines. The rails plugin new command creates the basic structure that all engines share. For a fully namespaced, mountable engine, you'll use the --mountable flag:
rails plugin new blorgh --mountable
This command generates a complete engine skeleton with the following structure:
- An
appdirectory tree with assets, controllers, helpers, jobs, mailers, models, and views - A
config/routes.rbfile configured for the engine - A
lib/blorgh/engine.rbfile that defines the engine class - A
lib/blorgh.rbfile that serves as the entry point - A
blorgh.gemspecfile for packaging as a Ruby gem - A
testdirectory with a dummy Rails application for testing
Understanding the Generator Options
The --mountable option includes all features of the --full option and adds namespace isolation. The --full option tells the generator you want a complete engine structure including the app directory, routes file, and engine definition file.
Key options include:
| Option | Description |
|---|---|
--full | Creates an engine with a complete Rails application structure |
--mountable | Adds namespace isolation to --full, creating a fully isolated mountable engine |
--skip-git | Skips git repository initialization |
--api | Creates API-only engine structure |
--skip-action-mailer | Skips Action Mailer components |
--skip-active-storage | Skips Active Storage components |
For most engine projects, --mountable is the recommended choice because it enforces clean isolation that prevents conflicts as your engine grows and as it's used across different host applications.
Inside an Engine: Critical Files and Directories
The Engine Class Definition
At the heart of every engine is the engine class definition, typically located at lib/blorgh/engine.rb. This file defines how Rails recognizes and loads the engine:
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
end
end
The isolate_namespace call is crucial--it ensures that all components of the engine are wrapped in the Blorgh module, preventing naming collisions with the host application or other engines. Without this, a model generated in the engine might be called Article instead of Blorgh::Article, and it could conflict with an Article model in the host application.
The engine class inherits from Rails::Engine, which notifies Rails that there's an engine at the specified path. Rails will then correctly mount the engine inside the application and add the engine's app directory to the load path for models, mailers, controllers, and views.
The Entry Point: lib/blorgh.rb
The lib/blorgh.rb file serves as the entry point that Bundler loads when the engine gem is included in an application:
require "blorgh/engine"
module Blorgh
end
This file requires the engine file and defines the base module. Some engines choose to use this file for global configuration options--a good practice if you want to offer configuration settings to host applications.
The Gemspec
The .gemspec file defines the gem's metadata, dependencies, and file manifest:
Gem::Specification.new do |s|
s.name = "blorgh"
s.version = Blorgh::VERSION
s.authors = ["Your Name"]
s.email = ["[email protected]"]
s.homepage = "http://example.com"
s.summary = "Summary of your engine"
s.description = "Detailed description of your engine"
s.files = Dir["{app,config,lib}/**/*"] + ["Rakefile", "README.md"]
s.add_dependency "rails", "~> 8.0.0"
end
This file controls how RubyGems and Bundler understand your engine's dependencies and what files to include when the gem is installed.
The App Directory Structure
Inside the app directory, engines mirror the structure of full Rails applications, but with important differences:
app/assets/blorgh/- Contains stylesheets and images in namespaced subdirectoriesapp/controllers/blorgh/- Contains engine controllers in ablorgh/subdirectoryapp/helpers/blorgh/- Contains helper modules in ablorgh/subdirectoryapp/jobs/blorgh/- Contains background jobs in ablorgh/subdirectoryapp/mailers/blorgh/- Contains mailers in ablorgh/subdirectoryapp/models/blorgh/- Contains models in ablorgh/subdirectoryapp/views/blorgh/- Contains views, plus alayouts/directory
By placing files under these namespaced subdirectories and using isolate_namespace, you prevent components from potentially clashing with identically-named elements within other engines or the host application.
The Test Directory
The test directory contains a cut-down Rails application at test/dummy that serves as a mounting point for testing the engine. This dummy application mounts the engine and provides a minimal Rails environment for running tests:
# test/dummy/config/routes.rb
Rails.application.routes.draw do
mount Blorgh::Engine => "/blorgh"
end
This setup enables comprehensive testing of the engine's functionality in an environment that closely mimics how it will be used in production. The test directory supports unit tests, functional tests, and integration tests just like a regular Rails application.
Building Engine Functionality
Generating Models in Engines
Generating models in engines follows the same pattern as regular Rails applications, but with namespaced results:
bin/rails generate model article title:string text:text
This generates:
- A migration at
db/migrate/[timestamp]_create_blorgh_articles.rb - A model at
app/models/blorgh/article.rb - Test fixtures and unit tests in the appropriate namespaced locations
The migration creates a table named blorgh_articles rather than articles, and the model class is Blorgh::Article rather than Article. This namespacing ensures isolation from the host application.
The model itself looks like:
module Blorgh
class Article < ApplicationRecord
has_many :comments
end
end
Note that the model inherits from ApplicationRecord, which in a mountable engine is Blorgh::ApplicationRecord rather than the host application's ApplicationRecord.
Generating Controllers and Views
Controllers in engines are generated with full namespacing:
bin/rails generate scaffold article title:string text:text
This creates:
- A controller at
app/controllers/blorgh/articles_controller.rb - Views in
app/views/blorgh/articles/ - Helper modules in
app/helpers/blorgh/articles_helper.rb - Route definitions in
config/routes.rb
The controller class is namespaced:
module Blorgh
class ArticlesController < ApplicationController
# Controller actions here
end
end
Views are located in the namespaced directory, so app/views/blorgh/articles/index.html.erb rather than app/views/articles/index.html.erb. This prevents view path conflicts with the host application or other engines.
Defining Engine Routes
Engine routes are defined in config/routes.rb within the engine:
Blorgh::Engine.routes.draw do
resources :articles
root to: "articles#index"
end
Routes are drawn on the Engine object rather than on Rails.application, confining them to the engine itself and allowing them to be mounted at specific paths in host applications.
The host application mounts the engine and makes its routes accessible at the mount point:
# In host application config/routes.rb
Rails.application.routes.draw do
mount Blorgh::Engine => "/blog"
end
This means the engine's articles are accessible at /blog/articles in the host application.
Creating Migrations for Engines
Migrations for engine tables are generated using the standard Rails migration generator, but they live within the engine's structure. When a host application wants to use the engine, it copies the migrations using:
rails blorgh:install:migrations
rails db:migrate
This command copies all migrations from the engine into the host application's db/migrate directory, renaming them with the engine name as a suffix. The first time this runs, it copies all migrations; subsequent runs only copy new migrations that haven't been copied yet.
For multiple engines, you can use:
rails railties:install:migrations
This copies migrations from all railties (engines) to the host application.
Testing Your Engine
Setting Up the Test Environment
Engine testing leverages the dummy Rails application created in the test/dummy directory. This application mounts the engine and provides a complete Rails environment for testing:
# test/dummy/config/routes.rb
Rails.application.routes.draw do
mount Blorgh::Engine => "/blorgh"
end
You can run migrations in this test environment, generate additional models and controllers for testing, and interact with the engine through its mounted routes. The test directory structure mirrors a standard Rails application, supporting unit tests, functional tests, and integration tests.
Running Engine Tests
The bin/rails command within the engine directory provides access to Rails commands:
bin/rails test # Run all tests
bin/rails test:test/models # Run model tests
bin/rails test:integration # Run integration tests
bin/rails db:migrate # Run migrations in test environment
bin/rails console # Open engine console
The console provides access to engine models with their proper namespacing:
Blorgh::Article.find(1)
# => #<Blorgh::Article id: 1 ...>
Functional and Integration Testing
Functional tests for controllers live in test/controllers/blorgh/ and follow standard Rails testing patterns. Integration tests in test/integration/ test the engine's behavior as mounted in the host application, exercising the complete request-response cycle including routing through the engine.
For comprehensive testing, you should test:
- Model functionality with unit tests
- Controller actions with functional tests
- Routing configuration
- View rendering
- Integration with host application features
Hooking Into Applications
Mounting the Engine
To use an engine in a host application, first add it to the Gemfile:
# For local development
gem 'blorgh', path: 'engines/blorgh'
# For published gem
gem 'blorgh'
Then run bundle install to install the gem. Next, mount the engine in the host application's routes:
Rails.application.routes.draw do
mount Blorgh::Engine => "/blog"
end
This makes the engine accessible at the specified path, with all its routes scoped under that mount point.
Running Engine Migrations in Host Applications
After mounting the engine, copy and run its migrations:
rails blorgh:install:migrations
rails db:migrate
The first command copies migrations from the engine to the host application's db/migrate directory. The migrations are prefixed with the engine name to distinguish them from host application migrations.
You can run migrations for a specific scope:
rails db:migrate SCOPE=blorgh
This is useful when you need to revert or manage engine migrations separately from the host application's migrations.
Configuration and Initialization
Engines can include initializers in their config/initializers directory, which run when the Rails application loads. This allows engines to configure themselves before they're used:
# engine/config/initializers/blorgh.rb
Blorgh.setup do |config|
config.some_setting = "value"
end
The host application can configure the engine in its own initializers:
# host application config/initializers/blorgh.rb
Blorgh.author_class = "User"
This pattern allows engines to define configuration options that host applications can customize without modifying the engine's code.
Accessing Host Application Classes
A common pattern is for engines to use classes from the host application. For example, an engine might need to associate records with users from the host application:
# In engine model
belongs_to :author, class_name: Blorgh.author_class
The engine defines a configurable author_class that the host application sets to its User class. This loose coupling allows the engine to work with different user implementations in different host applications.
For controllers, engine controllers can inherit from the host application's ApplicationController:
# engine/app/controllers/blorgh/application_controller.rb
module Blorgh
class ApplicationController < ::ApplicationController
end
end
This provides access to shared functionality like authentication and session management implemented in the host application.
Advanced Engine Development
Overriding Engine Functionality
Host applications can override engine components using Rails' autoloading mechanism. By creating files in the host application with the same path as engine files, the host application's version takes precedence:
- Override models by creating
app/models/blorgh/article.rbin the host - Override controllers by creating
app/controllers/blorgh/articles_controller.rb - Override views by creating view files in the host application's
app/views/blorgh/directory
This "monkey patching" approach allows customization without forking the engine, though it requires careful management to avoid conflicts when upgrading the engine.
Asset Management
Engines can include their own assets in app/assets, lib/assets, or vendor/assets. For mountable engines, assets are typically prefixed with the engine name:
app/assets/stylesheets/blorgh/application.css
app/assets/javascripts/blorgh/application.js
The host application includes engine assets by adding manifest file references:
//= link blorgh/application.css
Rails' asset pipeline handles compilation and fingerprinting for engine assets just as it does for application assets.
Avoiding Framework Loading with Hooks
For engines that don't need all Rails frameworks, configuration hooks allow selective loading:
module Blorgh
class Engine < ::Rails::Engine
# Only load specific frameworks
config.api_only = false
# Or configure which frameworks to load
config.after_initialize do
# Custom initialization after Rails is fully loaded
end
end
end
This approach reduces memory usage and startup time for engines that only need specific Rails components.
Publishing Your Engine as a Gem
When your engine is ready for distribution, publish it to RubyGems:
- Ensure your
.gemspecis complete with accurate metadata - Build the gem:
gem build blorgh.gemspec - Test the gem locally:
gem install ./blorgh-0.1.0.gem - Push to RubyGems:
gem push blorgh-0.1.0.gem
Once published, other developers can use your engine by adding it to their Gemfile without specifying a path.
Best Practices for Engine Development
Design Principles
Successful engines share several characteristics that make them maintainable and useful:
Single Responsibility: Each engine should focus on a single feature domain. Trying to build a "kitchen sink" engine that does everything leads to complexity and limits reusability. A blog engine should handle blogging, not authentication and e-commerce.
Configuration Over Hardcoding: Expose configuration options for anything that might vary between host applications. User class names, API keys, feature flags, and UI options should all be configurable.
Clear API Boundaries: Define clear interfaces between the engine and host applications. Document what classes, methods, and configuration options are public API versus internal implementation details.
Thorough Testing: Test your engine as if it were a standalone application. The dummy Rails app provides the environment you need--use it to write comprehensive tests that catch regressions before your users do.
Common Patterns and Anti-Patterns
Good patterns to follow:
- Use
isolate_namespacefor all mountable engines - Provide comprehensive configuration options for host customization
- Write migrations that can run multiple times safely (idempotent)
- Document integration requirements clearly for users
- Follow semantic versioning for releases
- Keep dependencies minimal and explicit
- Design for extension, not modification
Anti-patterns to avoid:
- Hardcoding host application class names without configuration
- Assuming specific routing or controller conventions
- Depending on host application specific features
- Creating tight coupling between engine components
- Neglecting to test with multiple Rails versions
- Forgetting to namespace assets and views
- Making assumptions about the host application's user model
Real-World Engine Examples
Authentication: Devise
Devise is one of the most popular Rails engines, providing comprehensive authentication functionality. It handles user registration, login, sessions, password resets, and account lockouts. Devise demonstrates how to build a sophisticated engine that integrates deeply with host applications while providing extensive configuration options. Study its architecture to understand how to build engines that feel native to any Rails application while maintaining separation of concerns.
Administration: Active Admin
Active Admin provides a complete admin interface as a mountable engine. It automatically generates admin panels for models, includes authentication integration, and offers extensive customization. Active Admin shows how to build engines that provide complete UI components along with backend functionality. Its use of namespacing and configuration patterns is exemplary for any engine developer.
Content Management: Refinery CMS and Spree
These engines demonstrate large-scale engine development for complex feature domains. They include multiple models, controllers, views, and integrate extensively with host applications. Studying these projects reveals patterns for building substantial engines that approach the complexity of full applications--useful reference points when your engine grows beyond a simple feature module.
Devise
Authentication engine handling registration, login, sessions, and password management for Ruby on Rails applications.
Active Admin
Admin interface engine with automatic model scaffolding, filtering, and extensive customization options.
Spree
E-commerce platform engine with cart, checkout, payment integration, and product management features.
Frequently Asked Questions
Conclusion
Rails engines represent a mature and powerful feature of the Rails ecosystem that enables true modularity in Rails applications. Whether you're extracting reusable functionality for multiple projects, building a SaaS platform that needs clear boundaries between components, or simply organizing a large application into maintainable pieces, engines provide the architectural foundation you need.
The key to successful engine development lies in understanding the balance between isolation and integration--using isolate_namespace to prevent conflicts while providing clear configuration interfaces for host applications to customize behavior. Following the patterns and practices outlined in this guide will help you create engines that are maintainable, testable, and genuinely reusable across multiple projects.
As you develop your own engines, remember that the Rails community has accumulated years of experience in this area. Study successful engines like Devise and Active Admin, contribute to open-source engine projects, and don't be afraid to refactor as you learn. The investment in proper engine architecture pays dividends in code reuse, team collaboration, and application maintainability.
For teams building complex Rails applications, our /services/web-development/ team has extensive experience designing and implementing engine-based solutions for enterprise applications. We can help you architect your codebase for long-term maintainability while accelerating development through reusable components. Additionally, if you're exploring ways to leverage AI and automation within your Rails architecture, our AI automation services can help you integrate intelligent features into your modular application design.
Sources
- Ruby on Rails Guides: Getting Started with Engines - Official Rails documentation covering engine architecture, namespace isolation, mounting, and advanced customization
- Honeybadger: Building Ruby on Rails Engines - Modern comprehensive guide covering engine types, real-world examples, and best practices
- RubyGems: Making Your Own Gem - Core gem creation workflow that engines extend for distribution
- Devise GitHub Repository - Example of popular Rails engine gem for authentication
- Active Admin GitHub Repository - Example of mountable engine for admin interfaces