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