I thought I'd come up with a slick way to extend ApplicationController in a Rails 3.x gem.
In my gem's lib/my_namespace/my_controller.rb
, I had:
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
but when the Gem is loaded, app/controllers/application_controller.rb
is not yet loaded, so it fails:
/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)
As a workaround, I had defined ApplicationController in my gem's lib/gem_namespace/application_controller.rb
as:
class ApplicationController < ActionController::Base
end
I assumed that even though I had defined it there, it would be redefined in my Rails 3 application's app/controllers/application_controller.rb
, such that both controllers in the application that extended ApplicationController
and controllers that extended MyNamespace::MyController
would directly or indirectly extend the ApplicationController defined in app/controllers/application_controller.rb
.
However, we noticed that after loading the gem, controllers that extend ApplicationController
were unable to access methods defined in app/controllers/application_controller.rb
. Also, the ApplicationHelper
(app/helpers/application_helper.rb)
module was no longer being loaded by other helper modules.
How can I extend ApplicationController
within the controller in my gem for the purpose of defining a before_filter
and after_filter
to and use initialize
to access the class's name to determine the associated model's class that it could then store and use within its methods?
Update 2012/10/22:
Here's what I came up with:
In 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
and in 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
For this specific kind of functionality I would recommend creating a module in your gem and include that module in your Application Controller
class ApplicationController < ActionController::Base
include MyCoolModule
end
To add before filters, etc (add this to your module)
def self.included(base)
base.send(:before_filter, my_method)
end
Update: you may be able to just do base.before_filter :my_method
which is cleaner.
Here is a Gist that shows how to access the class of the subclass and store it in an instance variable and access it in the before and after filters. It uses the include method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With