Ruby on RailsMiddleTechnical

Что такое Rails engines и как они помогают модуляризировать большие приложения?

Rails Engine — самодостаточный плагин со своими моделями, контроллерами, маршрутами и миграциями, монтируемый в родительское приложение. Isolated Engine полностью изолирует namespace через isolate_namespace, full engine разделяет пространство с хостом.

Rails Engines: самодостаточные мини-приложения

Rails Engine — это Gem, который содержит полноценное Rails-приложение: модели, контроллеры, вьюхи, маршруты, миграции, ассеты и даже initializers. Engine монтируется в родительское приложение и работает как изолированный модуль. Сам Rails — это тоже Engine (Rails::Application < Rails::Engine).

Типы Engines

  • Full Engine — разделяет пространство имён с родительским приложением. Модели, контроллеры и хелперы доступны без префикса.
  • Isolated Engine — полная изоляция через isolate_namespace. Свои маршруты, хелперы, ассеты не конфликтуют с хостом.

Создание Engine

# Создание нового Engine
rails plugin new billing --mountable
# --mountable = isolated engine с namespace
# --full = full engine без изоляции
# billing/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

    initializer 'billing.configure_stripe' do
      Stripe.api_key = Rails.application.credentials.stripe[:api_key]
    end
  end
end

Маршруты Engine

# billing/config/routes.rb
Billing::Engine.routes.draw do
  resources :invoices
  resources :subscriptions, only: [:index, :show, :create, :destroy]
  get  '/checkout', to: 'checkouts#new'
  post '/checkout', to: 'checkouts#create'
end
# Монтирование в родительское приложение
# config/routes.rb (host app)
Rails.application.routes.draw do
  mount Billing::Engine, at: '/billing'
  mount AdminPanel::Engine, at: '/admin'
end

Модели и миграции

# billing/app/models/billing/invoice.rb
module Billing
  class Invoice < ApplicationRecord
    self.table_name = 'billing_invoices'

    belongs_to :user  # ссылка на хост-модель
    has_many :line_items, class_name: 'Billing::LineItem'

    enum status: { draft: 0, sent: 1, paid: 2, overdue: 3 }

    monetize :amount_cents
  end
end
# Копирование миграций из engine в хост-приложение
bundle exec rake billing:install:migrations
bundle exec rails db:migrate

Подключение Engine в host app

# Gemfile хост-приложения
gem 'billing', path: 'engines/billing'       # monorepo
# или
gem 'billing', git: 'https://github.com/company/billing-engine'

Использование хост-моделей из Engine

# billing/lib/billing.rb
module Billing
  mattr_accessor :user_class
  self.user_class = 'User'  # дефолт

  def self.user_model
    user_class.constantize
  end
end

# В initializer хост-приложения
# config/initializers/billing.rb
Billing.user_class = 'Account'

Реальные примеры Engines в экосистеме Rails

  • Devise — engine для аутентификации (маршруты, контроллеры, вьюхи)
  • Spree — e-commerce engine
  • Administrate — admin-панель как engine
  • Sidekiq Web — Rack app, монтируется аналогично engine

Подводные камни

  • Миграции нужно копировать вручную: rails billing:install:migrations — этот шаг легко забыть при обновлении engine, и БД окажется устаревшей.
  • Конфликты имён таблиц: без явного self.table_name = 'billing_invoices' модель Billing::Invoice будет искать таблицу invoices, конфликтуя с хост-моделью.
  • Circular dependency между engine и хостом: engine не должен напрямую ссылаться на классы хоста (User, Account) — используйте конфигурируемые строки и constantize.
  • Хелперы и asset pipeline: при isolate_namespace хелперы engine доступны только через billing-префикс (billing.invoices_path), что неочевидно при отладке.
  • Тестирование engine требует dummy app: в spec/dummy или test/dummy должно быть тестовое Rails-приложение, что усложняет CI-пайплайн.
  • Версионирование и обратная совместимость: при использовании engine как gem нужно соблюдать SemVer — изменение схемы БД в engine без миграции ломает все хост-приложения.
  • Производительность autoload: каждый engine добавляет свои пути в Rails autoload, что может замедлять старт приложения при большом количестве engines.

Common mistakes

  • Сводить engines к названию метода без lifecycle и failure path.
  • Игнорировать модель runtime: Rails 8.1 строит приложение вокруг Rack, routing, controllers, Active Record, views и conventions over configuration.
  • Не отделять validation, authorization, transaction boundary и business logic.

What the interviewer is testing

  • Объясняет engines через конкретную точку lifecycle в Ruby on Rails.
  • Приводит корректный минимальный пример без вымышленных методов или callbacks.
  • Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.

Sources

Related topics