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.