Making Rails Better – ActionView Needs a Makeover

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 = {})
  def template_exists(template_path, extension = nil)

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)
    def render_javascript(javascript, status = nil,
                          append_response = true)
    def render_xml(xml, status = nil)
    def render_json(json, callback = nil, status = nil)

    def render_nothing(status = nil)

Followed by this ugliness in ActionView::Base:

module ActionView
  class Base
    def erb_template_exists?(template_path) 

    def builder_template_exists?(template_path) 

    def javascript_template_exists?(template_path)

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.

  1. Anders
    September 17, 2007

    Good post – as usual.

    Refactoring of the Rails framework into a more, well, framework-like structure is needed.

  2. September 17, 2007

    OK, so you’ve identified a problem. Instead of hoping someone else does the work, why not put together a patch and submit it to the Rails core team?

  3. September 17, 2007

    Alarm bells were definitely set off!

    As a developer for a Haml-heavy project, I can’t believe that 3rd party ActionView templates aren’t coded as classes which extend an AbstractTemplate-like class, but instead have so much rewritten code.

    For Rails to mature as a web framework, these oddities within the murky depths of its code must be rectified.

  4. she
    September 18, 2007

    “Instead of hoping someone else does the work, why not put together a patch and submit it to the Rails core team?”

    Before this preliminary work can be done, the maintainers must AGREE on this as a problem! Then there can be a solution.

  5. Crescent Fresh
    September 18, 2007

    > make the code a whole lot easier to follow and understand

    To ActionView’s credit, looking at their code it’s easy to see what’s happening (and thus criticize!). Abstraction or whatever other OO paradigm you throw at the problem might indeed satisfy the need for elegantly extending the framework, but these sorts of design-level solutions are notorious for complicating a codebase such that determining the best way/place to change/fix/optimize is hard for maintainers. The code becomes less and less self documenting.

    For the record I’m +1 on an the Abstraction solution. It’s a well known solution to this very common problem. Just thought the other viewpoint needed to be out there.

  6. September 18, 2007

    > Before this preliminary work can be done, the maintainers must AGREE on this as a problem! Then there can be a solution.

    Post it to rails-core and try to get some feedback. The core team will not do this on their own, they are plenty busy with other things. However if you can get some support then you can make it happen.

    But if you want to make Rails 2.0 you better hurry because there is a lot of talk about starting the countdown to an RC. Edge has a lot of new stuff on it that they want to get out, so if you have new features, they have to get in pretty soon.

  7. Charlie Savage –
    October 1, 2007

    Hi Brandon,

    Thanks for bringing it up to the Rails core team and volunteering to do the work. I’d be happy to help out testing and offering ideas. And maybe even some code if I can find a bit of time.