Eine Presenter Implementierung

Problematische Helper

Es besteht Konsens darüber, dass die View möglichst keine Logik enthalten sollte. Allerdings gibt es verschiedene Ansätze, wie das erreicht werden kann. Ruby on Rails bietet dafür Helper an.
Allerdings sind Helper oftmals problematisch. Sie sind nämlich standardmäßig in allen Views verfügbar. Das heißt, alle Helper teilen sich den gesamten Namensraum.
Die Problematik ist offensichtlich:

module ArticlesHelper
  def title text
    return if text.blank?
    content_tag(:h3, text)
  end
end

module ProductsHelper
  def title product
    link_to product.name, product, class: 'title'
  end
end

Der Benennung der Helper (title) ist nicht spezifisch genug. Je mehr Helper, desto wahrscheinlicher sind solche Dopplungen im Namensraum.
Außerdem sind Helper oftmals umständlich zu testen.

Definition Presenter

Beim Presenter pattern handelt es sich um eine Spezialisierung des Decorator pattern. Objekte werden nicht bloß einfach dekoriert, sondern im View Kontext präsentiert. Das heißt, ein Presenter Objekt enthält ausser dem zu dekorierenden Objekt noch den View Kontext (für die Rails Helper). Es vermittelt auf diese Weise zwischen View und Objekt.
Zum Einen kann so Logik sehr einfach objektspezifisch gekapselt werden und zum Anderen kann sie je nach Bedarf auch an einen bestimmten Kontext gebunden werden.
Die Kapselung der Funkionalitäten erleichtert in jedem Fall das isolierte Testen.

Ein problematisches View Beispiel

Ein Produkt Model:

$ rails g model Product name:string && rake db:migrate

Im View soll der Name unterschiedlich dargestellt werden, je nachdem, ob das Produkt neu ist oder nicht.
Verschachtelte Logik im View:

# app/views/products/show.html.haml
- if @product.in_range_at < 1.month.ago
  %h3.new= @product.name
-else
  %h3= @product.name

als auch die Kurzform:

# app/views/products/show.html.haml
%h3{ class: ('new' if @product.in_range_at < 1.month.ago) }= @product.name

sind inakzeptabel.

Eine Presenter Lösung

Eine einfache Presenter Klasse, von der abgeleitet werden kann, vereint das Objekt mit dem View:

class SimplePresenter
  attr_reader :model

  def initialize model, view
    @model = model
    @view = view
  end

  private

  def h
    @view
  end
end

Der ProductPresenter profitiert davon und enthält die entsprechende Implementierung der Überschriften Komponente:

class ProductPresenter < SimplePresenter
  NEW_RANGE = 1.month

  def name_tag
    css = 'new' if new?
    h.content_tag(:h3, model.name, class: css)
  end

  private

  def new?
    model.in_range_at < NEW_RANGE.ago
  end
end

und dessen Verwendung:

# app/views/products/show.html.haml
= ProductPresenter.new(@product).name_tag

Auf den ersten Blick sieht die Implementierung des Presenters umfangreich aus. Der Einsatz des Presenter patterns entfaltet seine Kraft allerdings bei komplexeren Komponenten und größeren Projekten.

Sinnvolle Helper

Natürlich machen Helper Sinn, wenn es sich um View Logik handelt, die Applikationsweit benötigt wird. Dazu gehören nicht nur die Rails Helper (z.B. FormTagHelper), sondern auch Applikationsspezifische Helper. Das Erstellen der Presenterinstanz sollte deshalb auch in einem Helper stattfinden:

# helpers/application_helper.rb
module ApplicationHelper
  def present(model, presenter_class=nil)
    presenter_class ||= "#{model.class}Presenter".constantize
    presenter_class.new(model, self)
  end
end

Die Präsentation der Objekte im View wird dadurch vereinfacht:

# app/views/products/show.html.haml
= present(@product).name_tag