Methoden-Ketten: Rubys Object#tap und Datenkapselung

Eine nette, mir bisher unbekannte Methode ist Rubys Object#tap, der man einen Block übergeben kann, in dem das Objekt als Argument zur Verfügung steht. Klingt im ersten Moment vielleicht unnötig, kann aber insbesondere in Views für etwas schöneren Code sorgen. Ein kleines Negativbeispiel in HAML:

%table
  %tbody
    %tr
      %td= current_company.name
      %td= current_company.owner ? "#{current_company.owner.first_name} #{current_company.owner.last_name}" : t('.unknown_owner')

Object#tap

Die ständige Wiederholung von current_company.owner streckt den Code. Gäbe es mehrere Besitzer, würde ein current_company.owners.each do |owner| Abhilfe schaffen und den Zugriff owner im folgenden Block bereitstellen. Tap macht genau das selbe, nur dass es auf jedem Objekt, also auch Nicht-Kollektionen funktioniert:

%table
  %tbody
    %tr
      %td= current_company.name
      - current_company.owner.tap do |owner|
        %td= owner ? "#{owner.first_name} #{owner.last_name}" : t('.unknown_owner')

Eine gute Zeile im Sinne der Lesbarkeit.

Information Hiding

Genau genommen ist es Glück, dass die Code-Stelle selbst mit dieser Änderung etwas unsauber aussieht. Sie zeigt uns ein Problem mit der Datenkapselung. Ein View für ein Unternehmen sollte nicht wissen müssen, aus welchen Details sich ein Darstellungsname des Besitzers ergibt. Hierfür bietet Rails die (an dieser Stelle etwas zu umständlichen) Partials oder Helper: display_owner(owner).

Decorators: Draper und Co.

Nun sind Helper für den objektorientierten Entwickler eine hässliche Sache, da sie auf Modulen basieren und nur eine Sammlung view-globaler Methoden darstellen. Einen besseren Ansatz bieten Decorators, die es für Rails etwa in Form von Draper gibt. Ist ein Owner dekoriert, ruft man im View z.B. owner.display_name oder gar owner auf. Für letzteres muss lediglich die Methode to_s des Dekorators überschrieben werden:

class OwnerDecorator < Draper::Decorator

  decorates :owner
  delegate_all
  include Draper::LazyHelpers

  def to_s
    if owner
      link_to "#{first_name} #{last_name}", owner_path(owner)
    else
      ""
    end
  end

end

Somit lässt sich an jeder Stelle denkbar einfach ein nullsicherer Link zu einem Besitzer einbinden. Auch Rechteverwaltung kann an dieser Stelle eingebracht werden, da der Dekorator Zugriff auf alle Helper und den Controller hat. Zudem lässt sich das ganze noch isoliert testen.

Sind die Dekoratoren korrekt konfiguriert, sieht das eingangs vorgestellte Problem nicht mehr wie eines aus:

%table
  %tbody
    %tr
      %td= current_company.name
      %td= current_company.owner