Decorators, Presenters, Delegators and Rails

#programming, #ruby, #ruby on rails

Here is a quite nice and simple way of using Ruby's SimpleDelegtor class as a Presenter in Ruby on Rails that I have been using recently to encapsulate some view related code from various separate places.

Decorating a wall

Why?

Model != View

Ideally you should decouple your Rails models from any kind of code that deals with presenting and displaying it's attributes; the model should not be interested in representing anything for the view and one way of doing this code is using a - lets call it a Presenter but it's just a class that represents display logic for a model.

We're not talking about moving away from Model View Controller, just separating and encapsulating code that will be used in views into one specific class rather than bolting it onto ActiveRecord models or adding to the generic dumping ground that the Rails helpers (app/helpers/*_helper.rb) can become.

Rails helpers

I think the Rails helper classes should generally be used for utility methods, not for specific use cases such as formatting model attributes for any kind of presentation logic. Keep them light and breezy.

View smells

Stuff like this in a view is a smell; too much in the wrong place; get it out of there.

<%= if @model.my_wonderful_attr.blank? then "Nothing here" else @model.my_wonderful_attr end %>

Testing

Yay! We can encapsulate these bits of logic and test them to death! We can fire all the test cases you can think of through them (yes, if you use helpers you can test them too but see above) and because we can unit test a Presenter its fast! We can continue to run these tests again and again, each time you run your unit test suite and catch bugs before any high level functional or acceptance tests.

Decorators and Presenters

A nice and simple way of creating a Presenter is to use Ruby's SimpleDelegtor class to implement the decorator pattern. The SimpleDelegator takes another object in it's constructor and will add functionality; thus decorating, whilst delegating all other method calls to this object. So based on this, we don't need to deal with all attributes, we can encapsulate methods intended for use by the view in this new class and keep the the model separate but still access properties.

Strictly speaking Decorators and Presenters are subtlety different and often open to interpretation but here we are using the decorator pattern to create a presenter; we are decorating an object for a specific purpose, that being used in the view.

An example model

class Car < ActiveRecord::Base

# example Car fields attr_accessible :make, :car_model, :colour

def lots_of_other_model_code # etc, etc
end end

Presenter implementation

require 'delegate'

class CarPresenter < SimpleDelegator

def description @description ||= "This is a #{model.make} #{model.car_model} of #{colour_or_empty} colour"
end

def colour_or_empty @colour ||= model.colour.blank? ? "unknown" : model.colour end

# Returns ref to the object we're decorating def model getobj end end

Unit tests # not an exhaustive set or sensibly implemented

require 'test_helper'

class CarPresenterTest < ActiveSupport::TestCase

def setup
    @car = cars(:car_one) # a car with Ford, Focus and Red attributes

    @decorated_car = CarPresenter.new(@car)
end

# a non-exhaustive set of tests for class

test &quot;should give description with correct attributes&quot; do
    assert_equal &quot;This is a Ford Focus of Red colour&quot;, @decorated_car.description
end

test &quot;should give description when colour is empty&quot; do
  @car.colour = nil # empty the colour attribute

  decorated_car = CarPresenter.new(@car)

  assert_equal &quot;This is a Ford Focus of unknown colour&quot;, decorated_car.description
end

end

Back to Rails

So, if you hadn't guessed already then you can use the Presenter as follows;

# car_controller.rb
car_to_decorate = Car.find(params[:id])
@car = CarPresenter.new(car_to_decorate)

# car/show.html.erb
<h3>
  <%= @car.description %>
</h3>

Result?!

So, I think using the decorator pattern is a really neat and simple way of implementing Presenters for Rails models;

Thanks

Thanks to someone called goldeneye for the [image].