Controladores de expansión de un Motor Rails 3 en la aplicación principal

Estoy usando un motor Rails como una gem en mi aplicación. El motor tiene PostsController con varios métodos y me gustaría ampliar la lógica del controlador en mi aplicación principal, por ejemplo, para agregar algunos métodos. Si solo creo PostsController en la aplicación principal, entonces el controlador del motor no está cargado.

Hay una solución propuesta en cuestión Motores de Rails que amplían la funcionalidad basada en la alteración de ActiveSupport::Dependencies#require_or_load

¿Es la única / correcta forma de hacer esto? Si es así, ¿dónde pongo esa pieza de código?

EDIT1:

Este es el código sugerido por Andrius para 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 

¿Por qué no simplemente heredar de la clase de controlador del motor en su aplicación (y apuntar sus rutas a los nuevos controladores secundarios)? Suena conceptualmente similar a la forma en que extiendes los controladores Devise integrados.

Por diseño, se supone que las clases en un Rails :: Engine tienen un scope para el motor. De esta forma, no introducen bugs extraños al pisar accidentalmente todo el código cargado en la aplicación principal o por otros motores. Monkeypatching ActiveSupport :: Dependencias para mezclar motores en general es una solución realmente mala.

Solo use Rails :: Railtie, en su lugar. Tienen todas la misma funcionalidad, pero no tienen el mismo scope que un motor. Usted tiene acceso a toda la stack de aplicaciones de Rails (incluidos los motores). Es un enfoque más quirúrgico.

 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 

En cuanto al diseño de archivos, las vulnerabilidades se ven exactamente como los motores.

Lectura adicional: Extendiendo Rails 3 con Railties

Y si todavía está confundido, eche un vistazo a este proyecto de git que tiene una implementación completa: https://github.com/jamezilla/bcms_pubcookie

Método 1

Esto es lo que puse en mi aplicación Rails 3 en application.rb luego de require 'rails/all' (avíseme si es un mal lugar para ponerlo)

 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 

Por un tiempo, esto no funcionó

 TypeError in PostsController#index superclass mismatch for class PostsController 

pero eso se debió a una clase de definición de class PostsController < ActionController::Base mal escrita class PostsController < ActionController::Base que debería ser class PostsController < ApplicationController

Método 2

Si no desea hacer esto para todos los controladores del motor, etc., puede cargar el controlador del motor antes de la definición en la aplicación principal

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

Creé una joya basada en el código de Andrius y Andrei mencionado anteriormente. En lugar de copiar alrededor de ese código, solo necesita la gem mixable_engines. Solo funciona con Rails 3 en este momento.

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andrei y @Artrius: te he acreditado en el archivo de licencia, avísame si quieres tu nombre real o algún otro crédito.

Si no desea que el soporte activo del parche cambie el orden de carga como se sugiere en la funcionalidad de ampliación de los motores Rails , puede utilizar un middleware de rack para la autenticación. Si la autenticación se realiza como parte de cada acción del controlador, este enfoque puede ahorrarle mucho código y tiempo.

@cowboycoded método 2 en conjunto con require_dependency y config.reload_plugins trabajado para mí en Rails 3.2.2 / Ruby 1.9.

Aquí está el código: https://stackoverflow.com/a/9790497/22237

Puede usar el método send() Ruby para inyectar su código en el controlador en el momento en que se crea el motor …

 # 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 

Este método le evita tener que redefinir la herencia del controlador dentro de su aplicación principal.