Let’s continue our on-again, off-again series about making Rails better. We’ve talked about fixing ActiveRecord‘s awkward handling of attributes, implementing RESTful error handling and using Content Negotiation with Rails. Today let’s dive back into ActionView.
This won’t make me many friends, but there isn’t a nice way to say this – the internals of ActionView are ugly. The fundamental problem is that ActionView lacks the right abstraction – in fact, it has no abstraction at all. Without a guiding design, ActionView descends into a mass of tangled, hard-to-pick apart code that lacks cohesion and has high coupling.
A Better Abstraction
What’s bizarre about this state of affairs is that the abstraction is easy to see.
What does ActionView do? It renders templates. Do templates changes – you bet! Rails has three built-in template types (erb, builder, rjs) and there are a myriad of 3rd party templates.
So quick – what’s the abstraction? Doh – an AbstractTemplate class of course. Its API would be something simple like this (note there might be one or two methods missing, I’d have to whip up a prototype to be sure):
class AbstractTemplate
def render(response, options = {}, assigns = {})
end
def template_exists(template_path, extension = nil)
end
end
Implementers could then inherit from the AbstractTemplate class when creating their own custom templates types and override the base functionality as needed.
Sadly, Rails doesn’t do this. Instead we are faced with this ugliness in ActionController::Base:
module ActionController class Base def render_text(text = nil, status = nil, append_response = false) end def render_javascript(javascript, status = nil, append_response = true) end def render_xml(xml, status = nil) end def render_json(json, callback = nil, status = nil) end def render_nothing(status = nil) end end end
Followed by this ugliness in ActionView::Base:
module ActionView
class Base
def erb_template_exists?(template_path)
end
def builder_template_exists?(template_path)
end
def javascript_template_exists?(template_path)
end
end
end
Just looking at the method names should set off alarm bells.
First, the division of labor between ActionController and ActionView is murky – resulting in tight coupling between them and lousy cohesion internally in both classes.
Second, its clear that Rails wasn’t designed to support multiple template types and they have been bolted on as ugly warts.
Two Code Paths
As I mentioned above, there are plethora of 3rd party template types. So how does ActionView supports support them? By punting – you register your template type and define a render method – then you are on your own.
Thus there are two separate code paths in ActionView for built-in templates and custom templates. But what’s the difference between a built-in template and a custom template? Nothing of course.
So why are there two code paths? Why can’t a custom template type reuse the applicable parts of ActionView (such as checking to see if a template exists on disk, caching a template, etc.).
Finishing Up
Introducing an AbstractTemplate class to ActionView would work wonders. It would make ActionView more extensible, simplify its interface, improve cohesion, reduce coupling and make the code a whole lot easier to follow and understand. So here is hoping the Rails core reworks ActionView for Rails 2.0 and expunges the ugliness that exists in it today.