Rails engines for modular applications

Sarah Mitchell Feb 2026
2 tabs
# Generate engine
# rails plugin new billing --mountable

# lib/billing/engine.rb
module Billing
  class Engine < ::Rails::Engine
    isolate_namespace Billing

    config.generators do |g|
      g.test_framework :rspec
      g.fixture_replacement :factory_bot
    end

    # Load migrations from engine
    initializer 'billing.load_migrations' do |app|
      unless app.root.to_s.match root.to_s
        config.paths['db/migrate'].expanded.each do |expanded_path|
          app.config.paths['db/migrate'] << expanded_path
        end
      end
    end

    # Load engine routes
    initializer 'billing.routes' do |app|
      app.routes.prepend do
        mount Billing::Engine => '/billing'
      end
    end
  end
end

# Engine routes (billing/config/routes.rb)
Billing::Engine.routes.draw do
  resources :subscriptions do
    member do
      post :cancel
      post :reactivate
    end
  end

  resources :invoices, only: [:index, :show]
  resources :payment_methods
end

# Engine controller
module Billing
  class SubscriptionsController < ApplicationController
    before_action :authenticate_user!

    def index
      @subscriptions = current_user.subscriptions
    end

    def create
      @subscription = current_user.subscriptions.build(subscription_params)

      if @subscription.save
        redirect_to billing.subscription_path(@subscription)
      else
        render :new
      end
    end

    def cancel
      @subscription = current_user.subscriptions.find(params[:id])
      @subscription.cancel!
      redirect_to billing.subscriptions_path
    end

    private

    def subscription_params
      params.require(:subscription).permit(:plan_id, :payment_method_id)
    end
  end
end

# Engine model
module Billing
  class Subscription < ApplicationRecord
    belongs_to :user, class_name: '::User'
    belongs_to :plan

    enum status: { active: 0, canceled: 1, expired: 2 }

    def cancel!
      update!(
        status: :canceled,
        canceled_at: Time.current
      )
    end
  end
end
2 files · ruby Explain with highlit

Rails engines are miniature Rails applications within applications. I use engines for extracting reusable functionality—authentication, billing, admin panels. Engines have their own models, controllers, views, routes, migrations. Mountable engines are fully isolated; regular engines share parent app's namespace. Engines enable modular monoliths—separation without microservices complexity. Testing engines independently ensures they're truly decoupled. Engines can be extracted to gems for sharing across projects. Engine migrations integrate with parent app via rake engine_name:install:migrations. Understanding engine isolation levels—full vs. partial—is key. Engines are powerful for large apps needing clear boundaries and reusability.