ZIDline
Ruby on Rails
Andreas Knarek
Ruby on Rails (kurz RoR oder Rails, www.rubyonrails.org) ist ein modernes, agiles Webframework nach dem MVC-Prinzip (Model-View-Controller). Es baut auf der Skript-Sprache Ruby (www.ruby-lang.org) auf und zeichnet sich im Wesentlichen durch die ideologischen Grundsätze „Convention over Configuration“ und DRY („don’t repeat yourself“) aus. Das Projekt TISS wird auf der technischen Basis von Ruby on Rails entwickelt.

Die Sprache – Ruby

Die objektorientierte Skript-Sprache Ruby wurde bis 1995 von einem einzelnen Entwickler, Yukihiro Matsumoto (kurz „Matz“) aus Merkmalen von mehreren bestehenden Sprachen wie Perl, Smalltalk, Eiffel, Ada und Lisp zusammengesetzt. Dabei wurde besonders darauf geachtet, eine sehr lesbare und einfache Sprache zu entwickeln, die auch komplexe Vorgänge mit wenigen kurzen Befehlen umsetzen kann. Seit 1995 erfreute sich Ruby jährlich immer größerer Beliebtheit und verbreitet sich seit dem Erscheinen des Webframeworks Ruby on Rails auch immer mehr im professionellen/kommerziellen Bereich. Außerdem ist eine sehr große OpenSource-Community um Ruby entstanden, die die Sprache laufend verbessert und weiterentwickelt.

In Ruby ist prinzipiell alles ein Objekt. Dadurch sind zum Beispiel auch Iteratoren für Datentypen wie Ganzzahlen möglich, die im ersten Moment zwar merkwürdig erscheinen, jedoch sehr lesbar sind und gegenüber „herkömmlichen“ for-Schleifen – die natürlich auch möglich sind – sehr viel kompakter sind:

3.times do { print ‚Hello’ }

Ruby erlaubt es außerdem, jede Methode und jeden Operator (sind in Ruby nichts anderes als Methoden) zu überschreiben und offenbart dadurch ungeahnte Flexibilität. Vererbung ist für eine objektorientierte Sprache natürlich Pflicht. Zusätzlich gibt es auch noch die Möglichkeit von Mixins. Dabei handelt es sich um Code-Module/Fragmente, die in beliebige Klassen und Objekte dynamisch dazugeladen werden können um diese zu erweitern bzw. auch Funktionalität zu ersetzen. Auch die Basisklassen von Ruby können beliebig erweitert und verändert werden, ohne mittels Vererbung eingreifen zu müssen.

 # Erweiterung des Datentypen Numeric
class Numeric
  def addiere(x)
    self.+(x)
  end
end

# Resultat
7.addiere 2
=> 9

Es gibt mittlerweile unzählige Module und Erweiterungen für Ruby, die es zu einer sehr mächtigen Sprache werden lassen. Durch die interpretierte Ausführung wird eine Plattformunabhängigkeit ähnlich wie bei Java erreicht. Aktuell ist Ruby für GNU/Linux, viele UNIX-Varianten, Mac OS X, Windows 95/98/Me/NT/2000/XP, DOS, BeOS, OS/2 etc. verfügbar. Für den Serverbetrieb wird allerdings ein Linux-System empfohlen, da dort die beste Performance erzielt wird.

Da Ruby mittlerweile auch ein großes Interesse in der Java-Community geweckt hat, existiert schon ein recht ausgereiftes Projekt namens jRuby (jruby.codehaus.org) das eine Ausführung von Ruby-Code in einer Java-VM erlaubt. Außerdem kann beliebig zwischen dem Kontext von Java und Ruby gewechselt werden – es ist also möglich, in Java Ruby-Bibliotheken zu benutzen und umgekehrt.

Um die Performance von Ruby weiter zu steigern, wird für das nächste große Ruby-Release bereits an einer Java-ähnlichen Bytecode-Generierung gearbeitet.

Das Webframework – Ruby on Rails

Rails wurde 2004 von David Heinemeier Hansson veröffentlicht. Im Gegensatz zu vielen anderen Webframe- works jedoch mit dem großen Unterschied, dass Rails aus einer bestehenden Business-Applikation extrahiert und nicht künstlich Schritt für Schritt aufgebaut wurde.

Rails implementiert das so genannte MVC-Paradigma (Model-View-Controller), das eine Applikation auf drei Schichten zerlegt:

  • Model-Layer: einfacher, zentraler Zugriff auf relationale Datenbanken (aktuell unterstützt: DB2, Informix, Firebird, MySQL, Openbase, Oracle, PostgreSQL, SQLite, MS SQL Server, Sybase).

  • Controller-Layer: Steuerungsschicht zum Kapseln von Business-Logik und Schnittstellen mit Unterstützung von mächtigen Routing-Mechanismen zur einfachen Implementierung von „pretty URLs“.

  • View-Layer: Unterstützung von verschiedenen dynamischen Ausgabeformaten wie HTML, XML/XHTML, JavaScript sowie Binärdaten.

Das vereinfachte Architektur-Bild, das wir für TISS einsetzen, kann wie folgt dargestellt werden:

Die blauen Bereiche stellen die Persistenzschicht (Model-Layer) dar, die grüne Schicht die Steuerungsschicht (Controller-Layer) und die violette Schicht schlussendlich die Templateschicht (View-Layer).

Model-Layer: ActiveRecord

Die Klasse ActiveRecord stellt in Rails das Datenbank-Backend dar. Es erlaubt neben den „einfachen“  Datenbankzugriffen SELECT, UPDATE, INSERT, DELETE auf einzelne Tabellen auch komplexe Darstellungen wie Aggregationen, Assoziationen und bietet auch ein eigenes datenbank-unabhängiges Migrationsframework an.

Beispiel für die Darstellung von Personen ? Tasks mit ActiveRecord und Migrations:

 # Anlegen der Datenbank-Tabellen
create_table :people do |t|
  t.column :name, :string, :limit=>50, :null=>false
end
create_table :tasks do |t|
  t.column :person_id, :integer, :null=>false
  t.column :name, :string, :limit=>255, :null=>false
  t.column :todo_until, :datetime
end
# ActiveRecord Models zum Datenbank-Zugriff aus der
# Applikation heraus
class Person < ActiveRecord::Base
  has_many :tasks
  validates_presence_of :name
end
class Task < ActiveRecord::Base
  belongs_to :person
  validates_presence_of :person_id, :name
end

Dieser Code reicht aus, um die notwendigen Datenbanktabellen in der konfigurierten Datenbank anzulegen, und alle möglichen Datenoperationen auszuführen:

 # Auslesen aller Personen mit dem Namen „Stefan“
personen = Person.find_all_by_name ‘Stefan’
# Auslesen der Person mit der ID 2
person = Person.find 2
# Auslesen aller Tasks der Person mit ID 2
person.tasks
# Anlegen einer neuen Person
person_neu = Person.create :name=>’Neue Person’
# Anlegen eines Tasks zu dieser neuen Person
person_neu.create_task :name=>’Neuer Task’
# Suchen aller Personen mit einem Task namens ‚Pause’
personen = Task.find_all_by_name(‚Pause’).collect
  { |p| p.person }

Neben den „einfachen“ Relationen wie 1:1, 1:n, m:n unterstützt ActiveRecord auch komplexere Konstrukte wie STI (single-table-inheritance) und polymorphe 1:n und m:n Relationen. Ein ausgeklügeltes Validation-Interface erlaubt außerdem eine zentrale Prüfung der Daten, bevor sie in die Datenbank persistiert werden. Mittels Callbacks und Observers kann auf verschiedenste Ak- tionen in der ORM-Schicht direkt reagiert werden. Zum Beispiel ein E-Mail an alle Admins, sobald ein neuer Benutzer angelegt wird. Üblicherweise würde eine solche Aktion in der „Benutzer-Anlegen-Logik“ implementiert werden. Wird dann von irgendwo anders aus ein Benutzer angelegt, erfolgt kein E-Mail-Versand. Durch die Platzierung direkt in der ORM-Schicht kann man dieses Problem elegant beseitigen.

Controller-Layer: ActionController

Die Klasse ActionController erlaubt das Implementieren von Funktionalität, die mittels HTTP-Request aufgerufen werden kann. Damit stellen die Controller-Klassen die zentrale Steuerung der Applikation dar. Mit ihnen können Webschnittstellen und dynamische Webseiten implementiert werden.

Für obiges Personen-Tasks-Beispiel würde der zugehörige Controller-Code für das Anzeigen, Editieren und Ändern/Speichern eines Tasks folgendermaßen aussehen:

class TaskController < ApplikationController
  # laden des referenzierten tasks, das dazugehörige
  # HTML-Template anzeigen.html.erb wird anschließend
  # automatisch geladen
  def anzeigen
    @task = Task.find params[:id]
  end
  def editieren
    @task = Task.find params[:id]
  end
  # laden und ändern des referenzierten Tasks – wenn
  # nicht gespeichert werden kann Fehlermeldung anzeigen
  def aendern
    @task = Task.find params[:id]
    if @task.update_attributes(params[:task])
      flash[:error] = ‘Speichern fehlgeschlagen’
      redirect_to :action=>:editieren, :id=>params[:id]
    else
      flash[:info] = ‘Task wurde gespeichert’
      render :action=>:anzeigen, :id=>params[:id]
    end
  end
end

 

View-Layer: ActionView

Die Klasse ActionView wird vom ActionController verwendet, um eine Antwort an den HTTP-Request zurückzuschicken. Dabei kann es sich um ein HTML-Template, XML-Template etc. handeln. Das HTML-Template für die Seite „anzeigen“ zu unserem Beispiel könnte wie folgt aussehen:

 <h1>Task-Details</h1>
<p>Inhaber: <%= @task.person.name %></p>
<p>Name des Tasks: <%= @task.name %></p>
<%= link_to ‚Task bearbeiten’, :action=>:editieren, :id=>@task

Durch das modular aufgebaute System können mit Hilfe der ActionView-Klasse nicht nur HTML-Seiten sondern auch andere Output-Formate wie xhtml, xml, javascript (für AJAX-Funktionen), csv, xls u.v.a. auf die gleiche Art erzeugt werden. Auch Methoden für wichtige Sicherheitsaspekte wie SQL-Injection, XSS (cross-site scripting), XSRF (cross-site request forgery) und Session-Hijacking werden von ActionView zur Verfügung gestellt. Außerdem bieten auch verschiedene Module/Plugins Möglichkeiten zur Internationalisierung (I18N) und Lokalisierung (L10N) der Applikation.

Automatisierte Tests

Da große Systeme auch große Mengen an Sourcecode hervorbringen, ist es für die Qualität des Systems von enormer Wichtigkeit, ein zuverlässiges und umfassendes Qualitätsmanagement durchzuführen. Rails unterstützt die Entwicklung an dieser Stelle mit einem Framework für automatisierte Whitebox-Tests. Diese sind direkt in die Applikation eingebunden und bieten eine unkomplizierte Möglichkeit, den entwickelten Code mit Test-Code zu verifizieren.

Rails bietet vier verschiedene Test-Arten an:

  • Unit-Tests: zum Testen der Model-Schicht und der daten-objekt-bezogenen Business-Logik.
  • Functional-Tests: zum Testen der Controller-Schicht mittels Simulation von HTTP-Zugriffen auf die Applikation.
  • Integration-Tests: zum Testen von komplexen Workflows innerhalb der Applikation durch Unterstützung von Multi-Session-Operationen.
  • Test-Mocks: zum Testen von externen, über Schnittstellen angebundenen Objekten und Simulation dieser Schnittstellen.

Neben den eigentlichen Test-Abläufen ist es natürlich auch notwendig, dafür geeignete Testdaten zur Verfügung zu haben, damit die Tests immer mit definierten Daten arbeiten können. Auch dazu bietet Rails in Form von so genannten Test-Fixtures eine Lösung an. Diese datei-basierenden Daten werden pro Datenbanktabelle in einer YAML-Datei („yet another markup language“) gespeichert. Mit wachsender Applikation wachsen auch diese Testdaten und erlauben so immer komplexere Tests.

TISS on Rails

Die Beweggründe für die Entscheidung, RoR für die Entwicklung von TISS zu verwenden, sind recht vielseitig. So hat sich in den vielen Jahren der Entstehung von TUWIS, TUWIS++ und anderen TU Wien-Systemen gezeigt, dass es notwendig ist, ein sehr flexibles, einfaches und somit wartbares Campussystem zu entwickeln, da es im Universitätsumfeld laufend zu Änderungen und Erweiterungen (neue Verordnungen, Gesetzesänderungen, Richtlinien sowie universitäts-interne Umstrukturierungen) kommt. Viele von diesen Aspekten müssen auch im Campussystem umgesetzt werden. Deshalb spielt die Wartbarkeit des Systems eine große Rolle.

Durch die unkomplizierte Abbildung von komplexen Datenstrukturen, Betriebsabläufen sowie die integrierten und ebenfalls sehr einfach definierbaren Selbsttests entsteht mit vergleichsweise wenig Aufwand ein umfangreiches System auf einer soliden Basis.

Um die Applikation auch für den Benutzer modern zu präsentieren, wird bei der GUI-Erstellung besonders auf die Einhaltung von HTML-Standards und speziell für TISS definierte Design-Guides geachtet.

Schnittstellen und Migration

Da TISS im Zuge einer „sanften Migration“ in Betrieb genommen wird, müssen eine Menge an Schnittstellen zu bzw. zwischen bestehenden Systemen erstellt werden, die nach vollständigem Abschluss der Migration zu TISS teilweise wieder deaktiviert werden. Der erste Migrations-Schritt kümmert sich größtenteils um die Stammdaten aller Personen, die an der TU Wien tätig sind bzw. mit der TU Wien in Berührung kommen. Neben den „hauseigenen“ Studierenden, Mitarbeitern und Lehrenden sind das z. B. auch externe Projekt-Mitarbeiter, Gastprofessoren, bis zu Kunden unserer Bibliothek.

Die erste sichtbare Erscheinung dieses Datenabbilds wird als Preview des neuen TU Wien Adressbuchs das Release des TISS-Adressbuchs im Sommer 2008 sein.

Zahlen und Fakten zum aktuellen Entwicklungsstand

Aktuell (Stand per 10. 6. 2008) ist bereits eine Menge Funktionalität in TISS entstanden. Die nachfolgende Tabelle soll einen kurzen Überblick darüber zeigen:

 

Anzahl
Datenbank-Tabellen171
Ruby-Klassen297
Ruby-Codezeilen11.506
Template-Dateien312

Entwicklungsteam

Unser Kern-Entwicklungsteam besteht derzeit aus sechs Entwicklern (alphabetische Reihung): Borovali Beytur-Deniz, Frühwirth Roland, Glaser Florian, Knarek Andreas (Entwicklungsleitung), Rajkovats Alexander, Vargason Robert.

Wir werden dabei von einem Team der Forschungsgruppe für Industrielle Software unter der Leitung von Thomas Grechenig (www.inso.tuwien.ac.at) unterstützt, das im Last- und Bedarfsfall direkt mitarbeitet an der TISS-Source, Prototypen für Requirements Engineering Workshops etabliert bzw. die Codierung in den Gesamtrahmen eines größeren Entwicklungsprojektes einbettet. Die konzeptiven Software Engineering Maßnahmen (wie Qualitätssicherung, Teststrategien, Releaseplanung, Wartung, Wiederverwendung), die über die ohnehin in RoR fest eingebauten Mechanismen hinausgehen, für die Langlebigkeit und Beweglichkeit des TISS-Systems aber unabdingbar sind, werden von Mario Bernhart aus dem INSO Team angeführt.

Resümee

Die Beispiele stellen natürlich nur sehr einfache Fälle von Applikationslogik dar, zeigen aber sehr deutlich, mit wie wenig Source-Code man in Rails auskommt.

Rails ist jedoch bei weitem mehr als die Versammlung der drei Basis-Klassen für das MVC-Paradigma. Es beinhaltet, wie bereits erwähnt, ein Framework zum automatisiert verwalteten Aufbau (Release-Management) der Datenbank, Internationalisierung der Applikation und vieles mehr.

Die Aufzählung von allen Möglichkeiten, die Rails mittlerweile bietet, würde den Rahmen dieser Zeitschrift bei weitem sprengen. Es gibt unzählige Erweiterungen für alle drei Schichten von Rails, die dem Entwickler das Leben leichter machen und komplexe Vorgänge auf ein Minimum an Aufwand reduzieren.