17

Pensé que se me ocurriría una forma elegante de extender ApplicationController en una gema Rails 3.x.¿Cómo puedo extender ApplicationController en una joya?

En tuve de lib/my_namespace/my_controller.rb mi joya:

class MyNamespace::MyController < ApplicationController 

    before_filter :some_method 
    after_filter :another_method 

    def initialize 
    # getting classname of the subclass to use for lookup of the associated model, etc. 
    # and storing the model_class in an instance variable 
    # ... 
    end 

    # define :some_method, :another_method, etc. 
    # ... 

private 
    attr_accessor :subclass_defined_during_initialize # etc. 

    # etc. 
end 

pero cuando se carga la Gema, app/controllers/application_controller.rb aún no está cargado, por lo que falla:

/path/to/rvm/gemset/gems/activesupport-3.2.6/lib/active_support/dependencies.rb:251: 
in `require': cannot load such file -- my_gem_name/application_controller (LoadError) 

Como solución, que había definido ApplicationController en mi gema lib/gem_namespace/application_controller.rb como:

class ApplicationController < ActionController::Base 
end 

I asumí que aunque lo había definido allí, se redefiniría en mi aplicación Rails 3 app/controllers/application_controller.rb, de modo que ambos controladores en la aplicación que extendía ApplicationController y los controladores que extendían MyNamespace::MyController extenderían directa o indirectamente el ApplicationController definido en app/controllers/application_controller.rb.

Sin embargo, notamos que después de cargar la gema, los controladores que extienden ApplicationController no pudieron acceder a los métodos definidos en app/controllers/application_controller.rb. Además, el módulo ApplicationHelper(app/helpers/application_helper.rb) ya no estaba siendo cargado por otros módulos auxiliares.

¿Cómo puedo extender ApplicationController dentro del controlador en mi joya para el propósito de definir un before_filter y after_filter y utilizar initialize acceder a nombre de la clase para determinar la clase del modelo asociado que luego podría almacenar y utilizar dentro de sus métodos?

actualización 22/10/2012:

Esto es lo que ocurrió:

En lib/your_gem_name/railtie.rb:

module YourGemsModuleName 
    class Railtie < Rails::Railtie 
    initializer "your_gem_name.action_controller" do 
    ActiveSupport.on_load(:action_controller) do 
     puts "Extending #{self} with YourGemsModuleName::Controller" 
     # ActionController::Base gets a method that allows controllers to include the new behavior 
     include YourGemsModuleName::Controller # ActiveSupport::Concern 
    end 
    end 
end 

y en lib/your_gem_name/controller.rb:

module YourGemsModuleName 
    module Controller 
    extend ActiveSupport::Concern 

    # note: don't specify included or ClassMethods if unused 

    included do 
     # anything you would want to do in every controller, for example: add a class attribute 
     class_attribute :class_attribute_available_on_every_controller, instance_writer: false 
    end 

    module ClassMethods 
     # notice: no self.method_name here, because this is being extended because ActiveSupport::Concern was extended 
     def make_this_controller_fantastic 
     before_filter :some_instance_method_available_on_every_controller # to be available on every controller 
     after_filter :another_instance_method_available_on_every_controller # to be available on every controller 
     include FantasticStuff 
     end 
    end 

    # instance methods to go on every controller go here 
    def some_instance_method_available_on_every_controller 
     puts "a method available on every controller!" 
    end 

    def another_instance_method_available_on_every_controller 
     puts "another method available on every controller!" 
    end 

    module FantasticStuff 
     extend ActiveSupport::Concern 

     # note: don't specify included or ClassMethods if unused 

     included do 
     class_attribute :class_attribute_only_available_on_fantastic_controllers, instance_writer: false 
     end 

     module ClassMethods 
     # class methods available only if make_this_controller_fantastic is specified in the controller 
     def some_fanastic_class_method 
      put "a fantastic class method!" 
     end 
     end 

     # instance methods available only if make_this_controller_fantastic is specified in the controller 
     def some_fantastic_instance_method 
     puts "a fantastic instance method!" 
     end 

     def another_fantastic_instance_method 
     puts "another fantastic instance method!" 
     end 
    end 
    end 
end 

Respuesta

5

Here is a Gist que muestra cómo acceder a la clase de la subclase y almacenarlo en una variable de instancia y acceder a ella en el antes y después de los filtros. Utiliza el método de inclusión.

+0

¡Impresionante! Incluir un módulo es realmente la mejor idea, entonces. ¡Muchas gracias por tu ayuda! –

+0

[Conversación relacionada en el foro de Rails] (https://web.archive.org/web/20130216193936/http://railsforum.com/viewtopic.php?pid=153813) –

8

Para esta especifi c tipo de funcionalidad que recomendaría la creación de un módulo de su joya e incluir ese módulo en su Controlador de aplicación

class ApplicationController < ActionController::Base 
    include MyCoolModule 
end 

Para añadir antes de filtros, etc (añadir esto a su módulo)

def self.included(base) 
    base.send(:before_filter, my_method) 
end 

Actualización: Es posible que solo pueda hacer base.before_filter :my_method que es más limpio.

+0

cabe indicar como un módulo realmente funciona en este caso? ¿Podrías ampliar cómo obtendrías el nombre de la subclase ya que no hay una subclase y cómo aprovecharías before_filter y after_filter? Eche otro vistazo a la pregunta y si puede ampliarla, por favor aclare. De lo contrario, asumiré que no es una opción. ¡Gracias! –

+0

Acabo de aclarar en la última línea de la pregunta que necesito usar before_filter, after_filter, y acceder al nombre de la subclase. –

0

Pude hacer referencia a ApplicationController con una devolución de llamada de inicializador.

código joya que subclases/referencias ApplicationController: código

class GemApplicationController < ApplicationController 
    before_filter :method_to_call 

    def method_to_call 
    #your code here 
    end 
end 

joya de devolución de llamada para crear controlador subclases:

module GemName 
    def self.load_gem_application_controller 
    require "path/to/gem_application_controller" 
    end 
end 

rails_app/config/inicializadores/gem_name.rb

GemName.load_gem_application_controller 

Luego, tenga controladores que utilicen esta subclase de la función GemApplicationController

class SpecialCaseController < GemApplicationController 
    # this will inherit from the gem's controller, 
    # which inherits from the rails_app ApplicationController 
end 
+0

Aprendí que es mejor usar módulos. Solo puede heredar de una clase principal (directamente), pero puede incluir/extender módulos tanto como desee. –

+1

Gracias Gary. Terminé implementando con un módulo también. –

2

La verdad es mucho más simple y flexible.

Añadir a lib/engine.rb esto: class Engine < Rails::Engine; end

Y a continuación, sólo tiene que utilizar:

ActionController::Base.class_eval do 

    include SomethingFromMineGemModule 

    # or: 
    def hello_from_gem 
    'Hey people!' 
    end 

end 
Cuestiones relacionadas