Estensione dei controller di un motore Rails 3 nell’app principale

Sto usando un motore Rails come un gioiello nella mia app. Il motore ha PostsController con una serie di metodi e vorrei estendere la logica del controller nella mia app principale, ad esempio per aggiungere alcuni metodi. Se creo PostsController nell’app principale, il controller del motore non viene caricato.

Esiste una soluzione proposta in questione Motori Rails che estendono funzionalità basate sulla modifica di ActiveSupport::Dependencies#require_or_load

È l’unico / modo corretto per farlo? Se sì, dove metto quel pezzo di codice?

Edit1:

Questo è il codice suggerito da Andrius per Rails 2.x

 module ActiveSupport::Dependencies alias_method :require_or_load_without_multiple, :require_or_load def require_or_load(file_name, const_path = nil) if file_name.starts_with?(RAILS_ROOT + '/app') relative_name = file_name.gsub(RAILS_ROOT, '') @engine_paths ||= Rails::Initializer.new(Rails.configuration).plugin_loader.engines.collect {|plugin| plugin.directory } @engine_paths.each do |path| engine_file = File.join(path, relative_name) require_or_load_without_multiple(engine_file, const_path) if File.file?(engine_file) end end require_or_load_without_multiple(file_name, const_path) end end 

Perché non ereditare solo dalla class controller di Engine nella tua applicazione (e indirizzare i tuoi percorsi ai nuovi child controller)? Sembra concettualmente simile al modo in cui estendi i controller Devise integrati.

In base alla progettazione, le classi in un Rails :: Engine devono essere associate al motore. In questo modo non introducono strani bug accidentalmente stomping su tutto il codice caricato nell’app principale o da altri motori. Monkeypatching ActiveSupport :: Le dipendenze per mescolare i motori su tutta la linea sono una soluzione davvero pessima.

Basta usare un Rails :: Railtie, invece. Hanno tutte le stesse funzionalità, ma non hanno lo stesso ambito di un motore. Hai accesso all’intero stack di app per rails (inclusi i motori). È un approccio più chirurgico.

 module MyModule module SomeModelExtensions # Called when this module is included on the given class. def self.included(base) base.send(:include, InstanceMethods) base.extend(ClassMethods) end module ClassMethods def some_new_class_method # do stuff... end end module InstanceMethods def some_new_instance_method # do stuff... end end end module SomeControllerExtensions def self.included(base) base.send(:include, InstanceMethods) base.alias_method_chain :new, :my_module end module InstanceMethods # override the 'new' method def new_with_my_module # do stuff end end end class Railtie < ::Rails::Railtie # The block you pass to this method will run for every request in # development mode, but only once in production. config.to_prepare do SomeModel.send(:include, MyModule::SomeModelExtensions) SomeController.send(:include, MyModule::SomeControllerExtensions) end end end 

Per quanto riguarda il layout dei file, i railty sembrano esattamente come i motori.

Ulteriori letture: estensione di Rails 3 con Railties

E se sei ancora confuso, dai un'occhiata a questo progetto git che ha una piena implementazione: https://github.com/jamezilla/bcms_pubcookie

Metodo 1

Ecco cosa ho inserito nella mia app Rails 3 in application.rb dopo aver require 'rails/all' (fammi sapere se è un brutto posto metterlo)

 require 'active_support/dependencies' module ActiveSupport::Dependencies alias_method :require_or_load_without_multiple, :require_or_load def require_or_load(file_name, const_path = nil) if file_name.starts_with?(Rails.root.to_s + '/app') relative_name = file_name.gsub(Rails.root.to_s, '') #@engine_paths ||= Rails::Application.railties.engines.collect{|engine| engine.config.root.to_s } #EDIT: above line gives deprecation notice in Rails 3 (although it works in Rails 2), causing error in test env. Change to: @engine_paths ||= YourAppName::Application.railties.engines.collect{|engine| engine.config.root.to_s } @engine_paths.each do |path| engine_file = File.join(path, relative_name) require_or_load_without_multiple(engine_file, const_path) if File.file?(engine_file) end end require_or_load_without_multiple(file_name, const_path) end end 

Per un po ‘questo non ha funzionato

 TypeError in PostsController#index superclass mismatch for class PostsController 

ma ciò era dovuto a una class di definizione di class PostsController < ActionController::Base digitata class PostsController < ActionController::Base che dovrebbe essere class PostsController < ApplicationController

Metodo 2

Se non si desidera eseguire questa operazione per tutti i controller motore ecc., È ansible caricare il controller del motore prima della definizione nell'app principale

 require PostsEngine::Engine.config.root + 'app' + 'controllers' + 'posts_controller' class PostsController < ApplicationController # extended methods end 

Ho creato una gem basata sul codice di Andrius e Andrei sopra. Invece di copiare il codice, è sufficiente la gem mixable_engines. Funziona solo con i binari 3 in questo momento.

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@ Andrei e @Artrius: ti ho accreditato nel file di licenza, fammi sapere se vuoi il tuo vero nome o qualche altro credito.

Se non si desidera che il supporto attivo della patch modifichi l’ordine di caricamento come suggerito nei motori Rails che estendono la funzionalità , è ansible utilizzare un middleware rack per l’autenticazione. Se l’autenticazione viene eseguita come parte di ogni azione del controller, questo approccio potrebbe far risparmiare molto tempo e codice.

Il metodo @cowboycoded 2 in congiunzione con require_dependency e config.reload_plugins funzionato per me su Rails 3.2.2 / Ruby 1.9.

Ecco il codice: https://stackoverflow.com/a/9790497/22237

È ansible utilizzare il metodo send() di Ruby per iniettare il codice nel controller al momento della creazione del motore …

 # lib/cool_engine/engine.rb module CoolEngine class Engine < ::Rails::Engine isolate_namespace CoolEngine initializer "cool_engine.load_helpers" do |app| # You can inject magic into all your controllers... ActionController::Base.send :include, CoolEngine::ActionControllerExtensions ActionController::Base.send :include, CoolEngine::FooBar # ...or add special sauce to models... ActiveRecord::Base.send :include, CoolEngine::ActiveRecordExtensions ActiveRecord::Base.send :include, CoolEngine::MoreStuff # ...even provide a base set of helpers ApplicationHelper.send :include, CoolEngine::Helpers end end end 

Questo metodo ti evita di dover ridefinire l'ereditarietà del controller all'interno della tua app principale.