07 Apr 2009
•
2 min read
Better Module Organization
As I said in my last post, Carl and I have been working on a more modular version of ActionController. As we fleshed out the feature set, we had a few needs that aren't directly addressed by Ruby's built-in feature-set.
- Modules occasionally depended on other modules (there's no point in having Layouts without Renderer), but including Renderer into Layout meant that we couldn't have setup on Renderer that got applied to the controller class itself. In this case, Renderer adds a "_view_paths" inheritable accessor to new Controller classes that is used to store a list of paths containing templates. If we included Renderer into Layouts, and then Layouts into ActionController::Base, that setup would happen on Layouts, which is wrong.
- We used the
def self.included(klass) klass.class_eval { ... } end
idiom a whole lot. In fact, that's the only thing we used the included hook for, except... - Extending ClassMethods onto the class.
I ended up with:
module AbstractController
module Callbacks
setup do
include ActiveSupport::NewCallbacks
define_callbacks :process_action
end
...
end
end
replacing:
module AbstractController
module Callbacks
def self.included(klass)
klass.class_eval do
include ActiveSupport::NewCallbacks
define_callbacks :process_action
extend ClassMethods
end
end
...
end
end
For dependencies, I replaced:
module AbstractController
module Helpers
def self.included(klass)
klass.class_eval do
extend ClassMethods
unless self < ::AbstractController::Renderer
raise "You need to include AbstractController::Renderer before including " \
"AbstractController::Helpers"
end
extlib_inheritable_accessor :master_helper_module
self.master_helper_module = Module.new
end
end
...
end
end
with
module AbstractController
module Helpers
depends_on Renderer
setup do
extlib_inheritable_accessor :master_helper_module
self.master_helper_module = Module.new
end
...
end
end
And finally, the Base controller itself could now be replaced with:
module ActionController
class Base2 < AbstractBase
use AbstractController::Callbacks
use AbstractController::Helpers
use AbstractController::Logger
use ActionController::HideActions
use ActionController::UrlFor
use ActionController::Renderer # just for clarity -- not required
use ActionController::Layouts
end
end
from:
module ActionController
class Base2 < AbstractBase
include AbstractController::Callbacks
include AbstractController::Renderer
include AbstractController::Helpers
include AbstractController::Layouts
include AbstractController::Logger
include ActionController::HideActions
include ActionController::UrlFor
include ActionController::Layouts
include ActionController::Renderer
end
end
It's not a tremendous change, but it definitely reduces the likelihood of accidental mistakes, and makes the actual usage a lot clearer. Of course, we will need to document this new mechanism, but it has already simplified the necessary mental model of the setup.
As always, thanks for reading!