Für die meisten Rails-Projekte verwende ich die gleichen Gems mit ähnlichen Konfigurationen. Um den Projektstart und den Update-Prozess zu vereinheitlichen, möchte ich eine Engine, die diese Abhängigkeiten und Konfigurationen übernimmt.
Für den einfacheren Einstieg erzeuge ich innerhalb einer bestehenden Rails-App eine neue Engine:
rails plugin new m3 --full
Diese wird zu Testzwecken im Gemfile des Elternprojekts verlinkt:
gem 'm3', path: 'm3'
Gem-Abhängigkeiten im Gemspec-File
Im Gemspec-File m3.gemspec steht bereits eine Rails-Abhängigkeit, die ich um die gewünschten Gems erweitere:
s.add_dependency "rails", "~> 4.2.4" s.add_dependency "simple_form", "~> 3.2.0" s.add_dependency "draper", "~> 2.1.0" s.add_dependency "cancancan", "~> 1.13.1"
Damit diese beim Start der Elternapplikation zur Verfügung stehen, müssen diese vom Gem geladen werden. Dies sollte in der Engine-Datei gemacht werden:
# <gem>/lib/<gem>/engine.rb require 'cancan' require 'draper' require 'simple_form' module M3 class Engine < ::Rails::Engine # ...
Die Elternapplikation kann nun wie gewohnt auf die Gems zugreifen.
Initializer
Erweiterte Konfigurationen für Simple-Form werden in zugehörigen Initializern festgelegt, die man ebenfalls einfach in der Engine anlegen kann:
# <gem>/config/initializers/simple_form.rb SimpleForm.setup do |config| # ...
# <gem>/config/initializers/simple_form_bootstrap.rb SimpleForm.setup do |config| # ...
Controller-Erweiterungen
Grundlegende Controller-Konfigurationen habe ich in einen Application-Controller mit Namespace gepackt:
# <gem>/app/controllers/m3/application_controller.rb class M3::ApplicationController < ActionController::Base # ...
Hier kann man auch wie gewohnt Concerns verwenden, diese werden ebenfalls automatisch gefunden.
Die fertigen Konfigurationen können dann vom Application-Controller der Elternapplikation verwendet werden:
# <app>/app/controllers/application_controller.rb class ApplicationController < M3::ApplicationController # ...
Module und Klassen aus lib bereitstellen
Auch in einer Engine landet unter lib so einiges, was nicht vom Autoloading abgedeckt wird. Diese könnten wie Gem-Abhängigkeiten explizit in der Engine-Datei geladen werden. Diese Lösung ist jedoch ein unschöner Workaround und sei hier nur für den Notfall aufgeführt.
# <gem>/lib/<gem>/engine.rb # Workaround require_dependency File.expand_path("../../../app/models/m3", __FILE__) require_dependency File.expand_path("../../../lib/m3/router", __FILE__)
Wie man sieht lade ich hier auch den Namespace M3, da Autoloading an dieser Stelle noch nicht funktioniert und die Lib-Klasse Routes entweder aufgrund des fehlenden Namespaces knallt oder diesen als leeres Modul anlegt. Letzteres kann zu einem schwer zu findenden Fehler führen, da die Modul-Datei im Verzeichnis app dann gar nicht mehr geladen wird.
Wie angedeutet ist dieser Ansatz wenig elegant. Die Require-Statements sollten besser zu einem Zeitpunkt ausgeführt werden, an dem Autoloading bereits zur Verfügung steht. Dann passiert vieles automatisch und übrige Requires können ohne File-Path-Expansions notiert werden:
# <gem>/lib/m3/router.rb require_dependency 'm3' class M3::Router # ...
# <gem>/config/routes.rb require_dependency 'm3/router' Rails.application.routes.draw do M3::Router.draw_routes(self) end