Sep 07 2007

Rails View Adapter

Closely related to the Presenter Pattern, we have been recently applying a similar approach in order to achieve thinner Controllers responding with name/value pair based data, in order to achieve Views which are decoupled from the rest of our application's layers.

The main ViewAdapter module looks something like this:

require "ostruct"

module ViewAdapter
  module ClassMethods
    def prepare(records)
      records.map do |record|
        view_data = OpenStruct.new
        yield view_data, record
        view_data
      end
    end
  end

  extend ClassMethods

  def self.included(receiver)
    receiver.extend(ClassMethods)
  end
end

Each View Adapter includes the ViewAdapter module which allows for a clean, easily testable, declarative setup of the View Data that will be eventually rendered on the screen.

class ConcertViewAdapter
  include ViewAdapter

   def concerts
     prepare Concert.find(:all) do |view_data, concert|
       view_data.artist = concert.artist
       view_data.venue = concert.venue
       view_data.price = concert.currency + concert.price
     end
   end
end

As a general rule, we maintain a one-to-one relationship between the Controllers and the corresponding View Adapters.

class ConcertsController < ApplicationController
  def index
    @concerts = ConcertViewAdapter.concerts
  end
end

These Adapters are mainly used to consolidate and format data, as returned by a Controller's action, for display.

Advantages/Trade-offs

Distinct decoupling of the View from the rest of the application layers which provides a single, well known point of maintenance to accommodate easy code changes or refactorings.

Established codebase location for view logic and formatting operations. Particularly useful for internationalization.

Easily testable. Tests for the View Adapters can run as part of a Fast Rails Test Suite because they don't require any Rails environment setup.

As a possible downside, this approach will break some of the standard Rails view helpers which depend on ActiveRecord objects being exposed in ERB templates. I personally do not consider this a big issue, because I don't find much benefit in most of these HTML helpers, especially considering the way they cross MVC boundaries by exposing Domain Objects to the View, or mask relatively straightforward mark up.