Despite being fully aware that controllers should include minimal logic, we still have some code that needs to be shared by multiple controllers (and even some views). The easiest way to share code in ruby is, of course, the mixin module. The trouble in rails is that any public methods on controllers are exposed as actions, even ones that come in via mixin. That's trouble.
One solution was to define all methods in our helpers as private. Didn't really work in every scenario and kind of limits testing. What we really wanted was to be able to include modules privately. As far as I can tell, ruby doesn't support this off the shelf, so we decided we give it a shot to see where it took us.
class Module
[:private, :protected].each do |type|
eval %{
def include_#{type}(*prms)
prms.each do |mod|
include(mod)
mod.instance_methods.each do |meth|
#{type}(meth)
end
end
end
}
end
end
This creates two methods include_private and include_protected. These guys wrap the normal include method but then take all the instance methods for the included module and privatize or protected-ize them, respectively.
Then we added this to ApplicationController:
class ApplicationController < ActionController::Base
class << self
alias_method :include_helper, :include_private
end
end
Now we can include helpers in our controllers privately:
class MyController < ApplicationController
include_helper MyHelper
end
Now, it seems to me that there must be a better way of doing this and we would love to see it because our googling came up empty and it seems odd that we had to resort to this, though it seems to fit our needs just fine.
2 comments:
I've taken to making submodules that have the private/protected methods, and then doing a class_eval, with the appropriate private/protected keyword before including the module.
Something like:
def self.included(base)
base.class_eval do
include InstanceMethods
protected
include ProtectedInstanceMethods
end
end
Another way which is close to the solution below, is to use Class::private which takes an Array of methods as argument.
def self.included(klass)
klass.class_eval do
private(:method, :another_method)
end
end
The difference is that you don't define a specific module just for your private methods.
So this can be good or bad depending on how you like to have things organized.
Post a Comment