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.