Ok, we're agreed that altering your host app's application_controller.rb isn't the way to go. Let's look at different ways of adding methods to the ApplicationController class (actually ActionController::Base) via a gem.
I've created a very simple gem. I want it to add one function, rot13, that means any controller will be able to call rot13('something!')
to get back 'fbzrguvat!'
.
(In real life you'd add this to String
...)
You could extend ActionController::Base
, like this:
class ActionController::Base
def rot13 str
a = 'a'.ord
z = 'z'.ord
str.unpack('c*').map { |x| (a..z).cover?(x) ? (((x - a) + 13) % 26) + a : x }.pack('c*')
end
end
And now in my application I can call rot13('ibvyn!')
inside any controller and voila!
It's safer to add a module and include it in ActionController::Base via a Railtie hook. So let's add a Railtie.
I add lib/rot13/railtie.rb
as follows:
module Rot13
class Rot13Railtie < Rails::Railtie
initializer "rot_13_railtie.extend_action_controller" do
ActiveSupport.on_load :action_controller do
# At this point, self == ActionController::Base
include Rot13::ControllerMethods
end
end
end
end
Now lib/rot13.rb
looks like this:
require "rot13/version"
require "rot13/railtie.rb" if defined? Rails
module Rot13
module ControllerMethods
def rot13 str
a = 'a'.ord
z = 'z'.ord
str.unpack('c*').map { |x| (a..z).cover?(x) ? (((x - a) + 13) % 26) + a : x }.pack('c*')
end
end
end
This is fine for most purposes.
Let's say you didn't want your rot13
method to be defined in ActionController::Base
and available to all controllers--let's say you wanted users of your gem to 'opt in' on a controller-by-controller basis, e.g.
class ApplicationController < ActionController::Base
with_rot_13
# and so on...
end
Instead of include Rot13::ControllerMethods
you might call extend Rot13::ControllerOptIn
within that on_load
block to add a with_rot_13
method at the class level of ActionController::Base
, then define a ControllerOptIn
module as follows:
module Rot13
module ControllerMethods
# ...
end
module ControllerOptIn
def with_rot_13
include ControllerMethods
end
end
end
That's it!
Edit: just to address the additional question of 'why isn't the method visible in my view?'--your method is not defined as a helper, so it's not automatically visible in your views without suffixing it with controller
, e.g. %p= controller.rot13 'blah'
. Luckily you can define it as a helper with a call to helper_method
, e.g.
module ControllerOptIn
def with_rot_13
include ControllerMethods
helper_method :rot13
end
end
Now you can do this (note: HAML):
%p= controller.rot13 'hello there!'
%p= rot13 'well how are ya?'
But it's not great to have to specify helper_method :rot13
here. Can you dig the necessary symbols straight out of Rot13::ControllerMethods
? Sure, as long as you're sure it's what you want:
module ControllerOptIn
def with_rot_13
include ControllerMethods
helper_method *ControllerMethods.instance_methods
end
end