Erfahrungsbericht: erste Schritte mit Git, Ruby on Rails und MongoDB (Teil 3)

In den ersten beiden Artikeln dieser Serie habe ich von meiner Hello-World-App und meinen ersten Erfahrungen mit Git erzählt. Heute schildere ich meine ersten Vergleiche zwischen PHP und Ruby bzw. Zend Framework und Ruby on Rails.

Ruby / Ruby on Rails statt PHP / Zend Framework

Ich habe den Einstieg in die RoR-Welt mit dem Buch „Agile Web Development with Rails“ von Sam Ruby (sowie Dave Thomas und David Heinemeier Hansson) gewagt. Wenn man im Web recherchiert, findet man über das Buch echt gute Kritiken. Meine Meinung: wer bereits fit in einer anderen Programmiersprache ist, bekommt hier ein solides Werk in die Hand gedrückt. Für Programmiereinsteiger gilt: erst einmal woanders anfangen.

Installation

Wie so oft in der Software-Welt beginnt die Geschichte mit einer Installation. Genauer gesagt der Installation von Ruby und Ruby on Rails. Mein Start war etwas holprig: die Anleitung ließ sich auf meinem Debian-Testsystem nicht 1:1 umsetzen und es wirkt mir etwas durchwachsen, wenn man raten muss, woher man denn nun am besten welche Ruby-Version zieht, welcher Befehl auf dem eigenen System zur erfolgreichen Installation und Aktualisierung der RubyGems (entfernt zu vergleichen mit PEAR-Packages) führt etc.

Das Boshafte: manche Probleme werden einem erst nach und nach bewusst. Meine Lehre daraus: für die Installation kein apt-get verwenden, sondern dieser Anleitung folgen. Das kenne ich von PHP anders. Zugegeben, die PHP-Version in Debian ist auch nicht unbedingt das Neueste vom Neuen. Meinem Eindruck nach bekommt man eine nicht ganz aktuelle Version bei RoR stärker zu spüren, weil sich das Framework noch in einer Phase rapider und nicht-abwärtskompatibler Änderungen befindet.

Jeder Rails-Kenner darf mich Lügen strafen, doch: wenn man sich die Suchergebnisse so ansieht, wirkt es, als ob die vielen nicht-abwärtskompatiblen Änderungen mehr auf überhasteter – oder sagen wir euphemistisch: jugendlich stürmischer – Veröffentlichungen beruhen als auf agilen Entwicklungsmethoden.

Also alles Böse?

Ruby

Nein, nicht alles Böse. Abgesehen von diesen Fehlzündungen am Start ist Ruby eine nette Sprache. Nett anzusehen und auch ganz nett konzeptioniert. Wobei ich noch nicht alle Konzepte restlos verinnerlicht habe, was auch daran liegt, dass ich bisher keine Unterlagen zu Ruby selbst studiert habe. Ich folgte eher dem agilen – oder jugendlich stürmischen – Ansatz und stürzte mich über das Rails-Buch mitten ins Geschehen. Dort werden die Grundlagen von Ruby kurz angesprochen, doch das Werk versteht sich explizit nicht als eine Ruby-Referenz.

Dennoch: für einen PHP-Programmierer, der einige Coding-Jahre auf dem Buckel hat, gibt es keine großen Umstellungsprobleme. Schließlich handelt es sich um keine funktionale Sprache. 😉

Der Code wirkt etwas übersichtlicher als bei PHP, da man sich Klammern an vielen Stellen ersparen darf. Das gilt zum Beispiel für Schleifen oder Kontrollstrukturen. Ein if-elseif-else ist ohne Klammern mindestens genauso lesbar, wenn der Code korrekt eingerückt wird.

Auch beim Aufbau von Klassen gibt es einige Möglichkeiten, Zeilen zu sparen und Übersichtlichkeit zu gewinnen. Zum Beispiel:

class Beispielklasse

  attr_accessor  :attribut1
  attr_reader    :attribut2
  attr_writer    :attribut3

  def methode1
    # code
  end

  protected

  def methode2
    # code
  end

  private

  def methode3
    # code
  end

end

Bei „attr_accessor“, „attr_reader“ und „attr_writer“ handelt es sich um Accessor-Methoden. Mit diesen Befehlen werden für die angegebenen Attribute automatisch Getter- und Setter-Methoden definiert. Attribut1 kann somit geschrieben und gelesen werden, Attribut2 nur gelesen und Attribut3 nur geschrieben.

Der Sichtbarkeitsbereich von Methoden kann zentral für einen Abschnitt vorgegeben werden und muss nicht bei jeder Methode aufgeführt werden. Methode1 hat die Sichtbarkeit „public“. Danach folgt das Keyword „protected“, das auf Methode2 angewendet wird. Methode2 ist somit protected. Dies gilt für alle folgenden Methoden der Klasse, solange nicht ein neuer Abschnitt definiert wird. Im Beispiel folgt „private“, was dann für Methode3 gilt.

Viel mehr Worte möchte ich an dieser Stelle zu Ruby als Sprache nicht verlieren.

Ruby on Rails

Dann also gleich weiter zu Rails: man hört ja geradezu wundersame Dinge, wie schnell man damit eine Anwendung zum Laufen bekommt. Und ich kann das bestätigen. RoR ist ein Framework, das eindeutig an der Praxis (und auch sinnvollen Prinzipien) orientiert ist, denn viele typische Probleme werden einem abgenommen. Und man merkt, dass die Entwickler mitdenken: wenn man sich nicht sicher ist, wie etwas funktioniert, schreibt man etwas nach Gefühl und siehe da: es läuft. 😉

Best Practices

Das Framework propagiert viele bewährte Entwicklungspraktiken. Zum Beispiel wird viel über Konventionen geregelt („Convention over configuration„): man findet sich schnell im Code auch von anderen zurecht, weil man weiß, wie bestimmte Dinge zu erwarten sind. Die Flexbilität geht einem eigentlich nicht ab, solange man sich im Rails-Universum bewegt. Das gleiche gilt übrigens für PHP-Frameworks mit diesem Ansatz. Wer sich dennoch einbildet, etwas daran ändern zu müssen, hat in mehreren Fällen über die Konfiguration die Möglichkeit dazu.

Rails legt Wert auf Separation of Concerns. Es wird streng getrennt zwischen den Model-, View- und Controller-Ebenen. Ein Beispiel: in meiner Hello-World-Anwendung benötigte ich mich (also meine User-Model-Instanz) in einem anderen Model. Nicht nachgedacht und Schwups, ich greife im Issue-Model auf die in der Session abgelegte User-Instanz des aktuellen Website-Besuchers zu. Aber nicht mit Rails: man bekommt eine Exception um die Ohren gehauen, weil Session-Daten nicht in Models und Observern verfügbar sind. Und natürlich ist das korrekt: braucht man im Model etwas in der Session, dann soll der Controller diese Daten an das Model übergeben. Solche kleinen Unsauberkeiten gehen in PHP-Frameworks normalerweise durch (weil es in PHP möglich ist).

Ein weiterer Punkt ist die testgetriebene Entwicklung (Test-Driven Development = TDD). Erzeugt man Code über Generatoren (dazu kommen wir gleich), so wird automatisch das Grundgerüst für die entsprechenden Unit-Tests erstellt. Auch funktionale Tests etc. sind vorgesehen. Die Tests können gleich mit Ruby on Rails ausgeführt werden, sobald das Projekt erstellt wurde: es ist keine Installation weiterer Software vonnöten.

Generatoren

Einer der Gründe, warum man mit Ruby on Rails schnell vorankommt, sind die Generatoren. Sie werden von Rails verwendet, um über die Kommandozeile neuen Code zu erzeugen. Ein Beispiel: wir möchten für unseren Issue-Tracker Projekte (als Datensätze) erstellen können. So eine Datenverwaltung besteht aus 4 typischen Schritten: Datensätze erstellen (create), lesen (read), aktualisieren (update)  und löschen (delete). Der Programmierer-Volksmund bezeichnet das als CRUD. In so manchem Framework würde man sich nun hinsetzen und das ausprogrammieren. Nicht so in Ruby on Rails: mit einem simplen Befehl stößt man den Scaffold-Generator an, der alles erstellt, was man so braucht: das Test-Gerüst, Datenbank-Anpassungen, den Code für Controller, View und Models. So hat man schnell eine erste Eingabemaske, auf der man aufbauen kann.

Natürlich gibt es nicht nur den Scaffold-Generator, sondern noch viele weitere, die einem Arbeit abnehmen. Wie das Schicksal es so will,  ist diese Basis aber nur bedingt sinnvoll, weil man den erzeugten Code dann immer von Hand an die jeweiligten Applikationsbedürfnisse anpassen muss. Halt, stimmt nicht nicht! Anstatt sich das jedes Mal wieder anzutun, kann man vorher stattdessen die Generatoren modifizieren und damit bestimmen, wie der generierte Code auszusehen hat. Damit wird dann gleich für jedes Scaffold der richtige Quelltext erstellt. Und falls das nicht geht, kann man immer noch komplett eigene Generatoren erstellen.

Jetzt wird der eine oder andere sagen: aber klar, das hab‘ ich in Zend Framework auch. Zend_Tool heißt das. Stimmt prinzipiell, aber falls sich in den letzten Monaten nicht gravierend etwas geändert hat, ist Zend_Tool noch ein Stück von den Rails-generate-Befehlen entfernt.

Ein Wermutstropfen ist mir bei der automatischen Code-Generierung aufgefallen. Bei der Code-Erzeugung über Generatoren werden auch Texte automatisch erzeugt, die man als User im Frontend sieht, zum Beispiel die Überschriften für die Verwaltungsoberfläche. Da die Variablen typischerweise englisch benannt werden, hat man somit überall im HTML englische Überschriften und Texte, was man als deutschsprachiger Entwickler oft nicht haben will. Wie man an diese Stellen leicht deutsche Übersetzungen reinbekommt, dazu weiß ich leider nichts. Vielleicht über angepasste Generatoren, denen man die deutsche Übersetzung zusätzlich übermittelt. Allerdings funktioniert die automatische Mehrzahlbildung etc. im Deutschen nicht so gut wie im Englischen.

Von Models, Views und Controllern

Wie erwähnt können über Generatoren die Grundgerüste für die drei Layer Model, View und Controller erzeugt werden.

Models werden über Klassen realisiert. Somit profitieren RoR-Models schon einmal über die gesteigerte Übersichtlichkeit, die ich im Kapitel über Ruby angesprochen habe. Einen weiteren Vorteil hat man, wenn man zur Speicherung der Daten (= Persistenz der Daten) schreitet. Rails geht davon aus, dass man Daten in Datenbanken speichert und bietet über Active-Record-Klassen eine Implementierung an, die mit Doctrine in PHP vergleichbar ist. Es ist möglich, Abhängigkeiten zwischen verschiedenen Models und somit Datenbank-Tabellen herzustellen. Dadurch werden die Daten korrekt abgespeichert, man kann automatisch auf die entsprechenden Models zugreifen usw. Wenn man will (oder muss), kann man auch komplexere Anfragen zusammenstoppeln. Ich bin noch nicht in extreme Tiefen vorgestoßen, aber ich habe den Eindruck, dass es sich um eine robuste Lösung handelt, mit der auch komplexere Modelle umgesetzt werden können. Auch der Wechsel von einer Datenbank zur anderen ist schnell geschehen, selbst man wie bei MongoDB mehr als die normale Active-Record-Klasse benötigt. In dieser Hinsicht ist Rails dem Zend Framework 1 überlegen; wenn Zend Framework 2 dann eine native Doctrine-2-Integration vorweisen kann, sind sie sicher auf Augenhöhe.

Neben Unit-Testing ist auch die Validierung der Daten bereits in Rails vorgesehen. Die Validierung erfolgt serverseitig, kann aber auch bereits auf der Client-Seite durchgeführt werden, um Eingabefehler vorzeitig abzufangen. Hier kommt dem Entwickler zugute, dass die Models komfortabel in der Erstellung von Formularen verwendet werden können.

Mit dieser eleganten Überleitung kommen wir zur View. In Rails gibt es wie auch in Zend Framework einen Ansatz, der immer gern wieder als „Two-Level-Layout“ bezeichnet wird. Der Ausdruck ist einigermaßen umstritten, jedenfalls handelt es sich um Folgendes: man kann für jede Seite ein Gerüst, ein Layout, definieren. Das Layout wird gerne verwendet, um an einer zentralen Stelle den Doctype sowie die unvermeidlichen <html>-, <head>- und <body>-Tags anzugeben. Dann gibt man vielleicht noch häufig auftretende Blöcke wie einen Header, einen Footer und eine Navigation fest. Sie werden in einem zweiten Schritt befüllt, da spricht man dann immer wieder von Templates. (Vielleicht ist es schon wem aufgefallen: auch in Magento wird das so gehandhabt.) Sprich: die Inhalte dieses Gerüsts werden dann seitenspezifisch gefüllt. Im Menü wird der aktuelle Eintrag hervorgeheben, im Hauptcontainer wird die Ausgabe der jeweiligen Seite angezeigt usw.

Nachdem wir hier eine Ähnlichkeit zwischen Ruby on Rails und Zend Framework festgestellt haben, sehen wir uns ein wenig RoR-Code an, um ihn mit Code in PHP / Zend Framework zu vergleichen.

<% if @issues.empty? %>
<p>Keine Issues.</p>
<% else %>
<table class="overview" id="issues">
  <thead>
    <tr>
      <th>#</th>
      <th>Projekt</th>
      <th class="primary">Titel</th>
      <th>Priorität</th>
      <th>Status</th>
      <th>Zuständig</th>
      <th>Aktualisiert</th>
      <th>Aktionen</th>
    </tr>
  </thead>

  <tbody>
  <% @issues.each do |issue| %>
  <tr class="<%= cycle('odd', 'even') %>" id="issue_<%= number_with_precision(issue.number, :precision => 0) %>">
  	<td><%= number_with_precision(issue.number, :precision => 0) %></td>
  	<td><%= issue.project.name %></td>
    <td class="primary"><%= link_to issue.title, issue_path(issue), { :title => 'Issuedetails anzeigen'} %></td>
    <td><%= render(:partial => "shared/priority", :object => issue.get_priority) %></td>
    <td class="status"><%= render(:partial => "shared/status", :locals => { :issue => issue}) %></td>
    <td><%= issue.responsible_user.login_name %></td>
    <td><%= issue.updated_at.strftime("%d.%m.%Y") %></td>
    <td><%= link_to 'Bearbeiten', edit_issue_path(issue), { :class => "link-icon edit", :title => "Bearbeiten" } %></td>
  </tr>
  <% end %>
  </tbody>
</table>
<% end %>

Dies ist der Inhalt meiner Datei app/views/shared/_issuelist.html.erb. Wie der Name vermuten lässt, dient sie dazu, an verschiedenen Stellen der Anwendung eine Liste von Issues anzuzeigen. Übrigens: wenn jemand Tipps hat, was an dem Code zu verschönern wäre, nur her damit.

Was sehen wir hier also im Vergleich zu PHP / Zend Framework?

  • Die Ruby-Code-Elemente stechen weniger aus PHP hervor. Das liegt sicher mitunter am kompakten <% %>. Solche Short-Tags gibt es in PHP auch, nur sind sie dort verpönt.
  • Weniger Klammern. Das haben wir schon erwähnt.
  • Wir sehen Kontrollstrukturen (if/else) und eine Iteration über die Issues-Variable, um die einzelnen Issues dazustellen. Die Syntax ist ein wenig anders als in PHP, aber sobald man sie kennt kein Problem.
  • In den folgenden Zeilen gibt es einige Hilfsfunktionen für die Ausgabe, das Äquivalent zu den View-Helpern in Zend Framework.
  • <%= @variable %> entspricht <?php echo $variable; ?>. Auch in PHP gibt es eine solche Kurz-Notation für echo  mit <?= und auch hier gilt: in PHP ist das nicht erwünscht.
  • Anfangs hat mich der Helper „cycle“ beeindruckt. Super, dachte ich mir, wie oft hätte ich mir so etwas schon fix eingebaut gewünscht. Das Rails-Buch hatte mich darauf gebracht, und das Helferlein ist auch praktisch. Dann bin ich draufgekommen, dass es das auch in Zend Framework schon geraumer Zeit gibt. Die Syntax ist in Rails vielleicht etwas schöner, weil man sich den Objektbezug mit $this-> spart.
  • Der Helper „link_to“ kommt sehr häufig zum Einsatz. Er ist meiner Meinung nach praktischer als der Url-Helper von Zend Framework, weil man ihm einfach ein Model hinschmeißen kann (noch einfacher als im Beispiel zu sehen) und er automatisch eine sinnvolle URL generiert. Ich habe mir noch nicht näher angesehen, wie das funktioniert, ich freue mich einfach darüber. 😉
  • Wie in Zend Framework können Partials verwendet werden, um Teile der View aus anderen Templates zu generieren.
  • Zuletzt möchte ich noch auf die Methode strftime (in der vorletzten Tabellenspalte) hinweisen. Wie man sieht, ist Ruby durchgehender objektorientiert als PHP. Mir persönlich gefällt das besser als die (historisch bedingte) Funktion in PHP.

Alles in allem: die View in Rails und Zend Framework ist funktional ziemlich ähnlich, sie ist in RoR vielleicht etwas hübscher anzusehen.

Zum Controller kann ich zum jetztigen Zeitpunkt nicht viel sagen. Im Prinzip gilt, was für Ruby-Klassen allgemein gilt. Da ich lieber schlanke Controller habe, habe ich bisher wenig darin programmiert und geschrieben. 😉

Im nächsten Teil

In Teil 4 der Serie setze ich bei Ruby on Rails fort. Es gibt noch einiges zu sagen, doch ich wollte den Artikel nicht zu lange werde lassen. Außerdem brauche ich Urlaub und steige gleich in einen Flieger. 😉 Ich würde mich freuen, wenn ihr mir in den nächsten 2 Wochen ein wenig Input zu diesem Artikel und Ruby / Ruby on Rails im Allgemeinen hinterlasst!

6 Antworten

  1. Kevin sagt:

    Hi Matthias,

    das war’s wohl leider mit deinen Erfahrungsberichten oder? Schade, hab zumindest keine weiteren hier im Blog gefunden.

    Gruss
    Kev

    • Hi Kev,
      richtig, ich habe nichts mehr geschrieben! Letztendlich hat das nicht gut genug zu meinem eigentlichen Blog-Thema gepasst.

      Grüße
      Matthias

  2. Daniel Lang sagt:

    Toller Artikel, v.a. sehr interessant, wie die Herangehensweise aus der Perspektive eines PHP-Profis ist. Freut mich, dass du so gut voran kommst! Ein paar Anmerkungen von mir dazu:

    @Scaffolding: In einem umfangreicheren Projekt, wirst du dir deine eigenen T4-Scaffolding-Templates anlegen. Dann macht das Ganze richtig Spaß…

    @TDD: Du vergewaltigst mir den Begriff „test-driven-development“! Einseits weil es sich beim Rails-Ansatz um normale Unit-Tests handelt (nicht test-first und damit noch lange kein TDD!) und andererseits weil Rails so ziemlich das beste Negativbeispiel für testbare Frameworks ist. Nachdem eine typische Rails-App in der Regel nur aus dem MVC-Pattern besteht (während das anderswo „nur“ der UI-Layer einer ganzen Applikation ist) gibt es eine sehr sehr enge Kopplung vom Model, View und Controller. Im Rails-Standardsetup ist es mangels IoC-Frameworks und Interface-Programmierung unmöglich eigene Stubs und Mocks einzuhängen, damit ein sinnvolles Testen möglich wäre. DHH spricht zwar gerne – selbstverliebt wie er ist – von agiler Entwicklung und Softwaretests, hat aber meiner Ansicht nach überhaupt keine Ahnung davon (wahrscheinlich auch der „echte“ Grund warum Basecamp und Highrise so reduziert sind).

    @Wechsel zwischen Datenbanken:
    Ein Wechsel zwischen ActiveRecords und einer Dokumentendatenbank mag zwar technisch machbar sein, bedeutet aber ein komplettes Umdenken in der Entwicklung, wenn davon einen Vorteil und nicht nur Nachteile haben will. Das Hauptproblem ist einfach, dass man im DDD-Sinn andere Aggregate Roots und damit ein anderes Domainmodel hat. Da hilft dann leider keine Sprache, kein Framework oder keine Datenbank mehr -> viel Arbeit wenn man eine bestehende Anwendung umbauen will.

    • Hi, dankeschön!

      @TDD: soweit ich bisher gesehen habe, ermöglicht RoR TDD. Das Werkzeug für die Entwicklung von Tests wird bereit gestellt. Man kann zuerst Tests schreiben, die fehlschlagen und anschließend den Code generieren, mit dem die Tests erfolgreich durchgeführt werden. Das ist mMn die Grundvoraussetzung für TDD. Wenn nun per Scaffolding die Models und die Dateien für die Tests in einem Rutsch erstellt werden, ist dieser Akt an sich kein TDD, aber mich würde interessieren, ob du wirklich das meinst.
      Zu Stubs und Mocks: das ist ein interessanter Einwand. Habe kurz überlegt und habe dazu wirklich noch nichts Erwähnenswertes mit bekommen. Wie sieht es bei Ruby on Rails mit Gems aus, die das nachrüsten? Nach kurzer Suche stößt man auf RSpec, das neben BDD auch Stub- und Mock-Funktionalität bietet. Falls du (oder jemand anderer) damit gearbeitet hat, würde mich eine kurze Meinung dazu interessieren.

      @Wechsel zwischen Datenbanken:
      ich bezog mich dabei mehr auf den Wechsel zwischen RDBMS (relationalen Datenbankmanagementsystemen). Klar, wenn ich ein System mit anderer Ideologie wähle, dann hat das grundlegende Auswirkungen. Das würde ich aber nicht auf RoR beschränken.

  3. tehael sagt:

    Ich denke je weiter du mit deinem RoR-Projekt voranschreitest, desto mehr wirst du die Eleganz des Frameworks und der Sprache schätzen lernen. Auch das Ökosystem rund um RoR mit seinen vielen Gems begeistert.

    Ein paar Tipps:
    Du verwendest number_with_precision(num, :precision => 0) mehr als einmal. Das könntest du in einen eigenen View-Helper packen. Da recht allgemein bspweise in app/helpers/application_helper.rb

    def no_precision_number num
    number_with_precision num, :precision => 0
    end

    Du kannst dir auch strftime sparen. Die I18n-Integration (bei RoR3 out of the box) stellt dir in Views die Methode l() als Kurzform von I18n.localize() zur Verfügung. Wenn du da eine Date oder Time rein steckst, bekommst du ein entsprechend der eingestellten Locale formatiertes Datum oder eben eine formatierte Zeit (es gibt auch coole Helper für zeitliche Distanzen). Wie diese Formatierung von statten geht kann in config/locales/[locale].yml definiert werden.
    I18n RoR-Guide, Standard Locale YAMLs für RoR in vielen Sprachen/Locales

    Viel Spaß mit RoR weiterhin…

    • Hallo,
      entschuldige die verspätete Freischaltung – ich war auf Urlaub. 🙂 Danke für deine Tipps, genau so etwas habe ich mir vom Artikel erhofft! Ich glaube gerne, dass man so einiges noch eleganter lösen kann – bin schon gespannt, auf was ich alles stoßen (bzw. gestoßen) werde.