ruby-on-rails - las mejores gemas de ruby
¿Cómo puedo extender ApplicationController en una gema? (4)
Pensé que encontraría una forma elegante de extender ApplicationController en una gema Rails 3.x.
En lib/my_namespace/my_controller.rb
mi gema, tuve:
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, la app/controllers/application_controller.rb
aún no está cargada, 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 temporal, había definido ApplicationController en lib/gem_namespace/application_controller.rb
mi gema como:
class ApplicationController < ActionController::Base
end
Asumí que aunque lo hubiera definido allí, se redefiniría en la app/controllers/application_controller.rb
mi aplicación Rails 3, de modo que tanto los controladores de la aplicación que extendían ApplicationController
como los controladores que extendían MyNamespace::MyController
amplíe 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 de ayuda.
¿Cómo puedo extender ApplicationController
dentro del controlador en mi gema con el fin de definir un before_filter
y after_filter
y usar la initialize
para acceder al nombre de la clase para determinar la clase del modelo asociado que luego podría almacenar y usar dentro de sus métodos?
Actualización 2012/10/22 :
Esto es lo que se me 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
La verdad es mucho más simple y flexible.
Agregue a lib/engine.rb
esto: class Engine < Rails::Engine; end
class Engine < Rails::Engine; end
Y luego simplemente use:
ActionController::Base.class_eval do
include SomethingFromMineGemModule
# or:
def hello_from_gem
''Hey people!''
end
end
Para este tipo específico de funcionalidad, recomendaría crear un módulo en su gema e incluir ese módulo en su Controlador de aplicaciones
class ApplicationController < ActionController::Base
include MyCoolModule
end
Para agregar filtros anteriores, etc. (agregue esto a su módulo)
def self.included(base)
base.send(:before_filter, my_method)
end
Actualización: es posible que solo base.before_filter :my_method
hacer base.before_filter :my_method
que es más limpio.
Pude hacer referencia a ApplicationController con una devolución de llamada de inicializador.
código de gema que subclases / referencias ApplicationController:
class GemApplicationController < ApplicationController
before_filter :method_to_call
def method_to_call
#your code here
end
end
código de llamada de devolución de llamada para crear un controlador subclasificado:
module GemName
def self.load_gem_application_controller
require "path/to/gem_application_controller"
end
end
rails_app / config / initializers / gem_name.rb
GemName.load_gem_application_controller
Luego, tenga controladores que usen esta subclase de funcionalidad GemApplicationController
class SpecialCaseController < GemApplicationController
# this will inherit from the gem''s controller,
# which inherits from the rails_app ApplicationController
end
Aquí hay una Gist que muestra cómo acceder a la clase de la subclase y almacenarla en una variable de instancia y acceder a ella en los filtros de antes y después. Utiliza el método de inclusión.