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 = {})
  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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Top