ZIDline
TISS Datenstruktur – Datenstruktur-Browser
Andreas Knarek
Ruby on Rails (kurz RoR oder Rails, www.rubyonrails.org) ist ein modernes, agiles Webframework nach dem MVC-Prinzip, wobei MVC für die Aufteilung in die drei Schichten Models (Datenstruktur, Datenlogik), Views (Templates für HTML, PDF etc.) und Controller (Ablaufsteuerung der Software, Business-Logik) steht. Vor allem die Model-Schicht – auch Persistenzlayer oder ORM (object-relational mapping) genannt – ermöglicht in RoR neben der grundlegenden Funktionalität des Datenbankzugriffs eine Menge weiterführender Features. Einige dieser Aspekte werden in diesem Artikel etwas genauer betrachtet. Zu guter Letzt findet eine kurze Vorstellung des bald freigegebenen Datenstruktur-Browsers statt.

Datenbankzugriff

Die Grundanforderung für ORM ist der einfache Zugriff auf die Datenbank der Applikation. Da moderne Software meist objektorientiert entwickelt wird, sollen auch Daten als Objekte dargestellt werden. Der Entwickler soll nicht genötigt werden, sich um den SQL-Zugriff auf eine Datenbank kümmern zu müssen.

Rails kapselt die Datenbanktabellen in ActiveRecord-Klassen, wobei Tabellenspalten als getter/setter-Methoden abgebildet werden. Diese Methoden müssen allerdings nicht – wie in anderen Sprachen meist üblich – definiert oder konfiguriert werden, sondern die Applikation liest die Tabellenstruktur beim ersten Zugriff aus, und erzeugt pro Spalte dynamisch eine entsprechende getter- und setter-Methode.

Mittels der Methode „find“ kann man Datensätze laden, über die getter und setter auf deren Attribute zugreifen und mittels „save“ wieder zurück in die Datenbank speichern. Daneben gibt es auch Methoden zum Löschen, direkten Updaten und Massenupdate von Datensätzen.

Nachfolgendes Beispiel zeigt eine Tabelle „person“, die zugehörige ActiveRecord-Klasse und ein paar Beispielzugriffe auf die Tabelle:

Tabelle person: 

idinteger
vornamevarchar2(100)
nachnamevarchar2(100)
nationalitaet_idinteger

Daten:

idvornamenachnamenationalitaet_id
1MaxMustermann1
2MissMusterfrau1

# ActiveRecord-Klasse
class Person < ActiveRecord::Base
end

# Beispiel
p = Person.find(1) # Lade Datensatz mit ID 1
p.vorname
=> Max
p.nachname # Getter-Zugriff
=> Mustermann
p.vorname = "Test" # Setter-Zugriff
p.save # speichert in die Datenbank

Suchen nach bestimmten Daten

Ein „find“ kann natürlich nicht nur über die ID erfolgen, sondern auch über Suchkriterien. Die folgende Anweisung sucht nach Datensätzen mit Vornamen „Miss“ und liefert den ersten gefundenen Satz zurück:

p = Person.find( :first, :conditions=>["vorname = ?", "Miss"] )

Will man alle Datensätze bekommen, auf die die Einschränkung zutrifft, kann man anstatt „:first“ ein „:all“ angeben.

Dynamische Finder

Da man in Rails bestrebt ist, sehr lesbaren Code zu schreiben, ermöglicht ActiveRecord auch den Datenzugriff über so genannte dynamische Finder. Diese erlauben Abfragen wie zum Beispiel:

p = Person.find_by_vorname "Miss"
p = Person.find_by_vorname_and_nachname "Max", "Mustermann"

Durch die Anwendung von dynamischen Findern wird der Programmcode um einiges kompakter und lesbarer.

Validierung von Daten

Ein wichtiger Aspekt eines ORM sind Datenvalidierungen. Hier geht es darum, neue / geänderte Daten nicht ungeprüft in die Datenbank zu schreiben. Dazu gehören zum Beispiel Not-Null-Checks, Textlängen-Validierungen, Wertebereichs-Validierungen bei Zahlen / Datum etc.

Damit die Validierungen nicht von einzelnen Applikationsteilen irrtümlich übergangen oder vergessen werden können, werden diese Vorgaben zentral in den Models implementiert. Diese werden immer dann ausgeführt, wenn ein Objekt gespeichert wird. Bei Fehlern wird das Speichern verhindert und eine entsprechende Fehlermeldung an die Controller / Views zurückgegeben.

Beispiele:

class Person < ActiveRecord::Base
  validates_presence_of :nachname
  validates_length_of :vorname, :within=>10..20
  validates_numericality_of :nationalitaet_id
end

Neben einigen vordefinierten Validierungen können auch ganz individuelle Codesegmente verfasst werden, die beim Speichern durchlaufen werden sollen. Somit ist größtmögliche Flexibilität gewährleistet.

Abbildung von Relationen

Relationen zwischen Objekten (also Tabellen) werden ebenfalls in den Models definiert. RoR unterstützt folgende Relationsarten:

  • 1:1
    Ein Objekt besitzt kein oder maximal ein Kindobjekt. z. B.: ein Mitarbeiter hat keinen oder genau einen Dienstausweis.
  • 1:n
    Ein Objekt besitzt kein, ein oder mehrere Kindobjekte. z. B.: eine Person hat keine, eine oder viele Telefonnummern.
  • n:m
    Ein Objekt besitzt kein, ein oder mehrere Kindobjekte, welche gleichzeitig auch anderen Objekten gehören können. z. B.: eine Person kann in mehreren Räumen einen Arbeitsplatz haben, wobei auch andere Personen in diesen Räumen arbeiten können.
  • polymorphic 1:n
    Ein Kindobjekt ist zu einem Objekt beliebiger Art zuordenbar. z. B.: nicht nur eine Person kann eine / mehrere Adressen haben, sondern auch eine Organisationseinheit, ein Gebäude etc. Adresse wird jedoch nur ein einziges Mal definiert.
  • polymorphic m:n
    Analog zu „polymorphic 1:n“

Fügen wir als Beispiel zu unserer Person noch eine Klasse Nation (Tabelle mit Spalte ID und NAME) hinzu, und verknüpfen diese beiden:

class Person < ActiveRecord::Base
  belongs_to :nation, :foreign_key=>"nationalitaet_id", :class=>"Nation"
end

class Nation < ActiveRecord::Base
  has_many :personen, :foreign_key=>"nationalitaet_id", :class=>"Person"
end

Durch diese „Verknüpfung“ werden dynamisch im Hintergrund etliche Methoden erzeugt, um auf die in Beziehung stehenden Objekte zugreifen zu können, direkt neue Objekte anzulegen, Objekte zu zählen etc.

Beispiele:

# Neue Nation anlegen
n = Nation.create(:name=>"Österreich") # bekommt die ID 1
# Person laden, und Nation zuordnen
p = Person.find(1)
p.nation = n # ident mit p.nationalitaet_id = n.id
p.save

# Person neu auslesen, Nationalität abfragen
p.reload
p.nation.name
=> Österreich

# Nation laden, und alle zugehörigen Personen auslesen
n = Nation.find(1)
n.personen.count
=> 13
n.personen
=> #Array von Personen

Callback-Methoden

Eine weitere wichtige Funktionalität eines ORM sind so genannte Callback-Methoden. Im Wesentlichen handelt es sich dabei um Observer, die Änderungen an Objekten registrieren und beliebige Methoden aufrufen können.

Die nachstehenden Callback-Methoden sind die vier gebräuchlichsten, es sind jedoch noch weitere vier in RoR verfügbar:

  • before_validation
    Wird direkt nach einem „save“ aufgerufen, noch bevor die Daten validiert werden.
  • after_validation
    Wird unmittelbar nach der Validierung aufgerufen, sofern der Datensatz gültig war und gespeichert werden kann.
  • before_save
    Wird aufgerufen, bevor der Datensatz letztlich in die Datenbank zurück geschrieben wird. Somit die „letzte Chance“, noch Zusatzdaten wie Datum der letzten Änderung, ID der ändernden Person etc. zu setzen.
  • after_save
    Wird aufgerufen, nachdem der Datensatz erfolgreich in der Datenbank gespeichert wurde. Hier können zum Beispiel Zähler inkrementiert werden.

In TISS werden zum Beispiel per Default in fast allen Models Änderungshistorien geschrieben. Dazu kann pro Model ein Mixin (Modul) hinzugeladen werden, das Änderungen (Anlegen, Ändern, Löschen) automatisch in einer History-Tabelle mitprotokolliert. Dieses Mixin verwendet dazu die before_save Callback-Methode.

Überblick durch Datenstruktur-Browser

Neben all diesen Punkten sind noch viele weitere Funktionen wie z. B. Internationalisierung und Unit-Tests in unsere Model-Schicht implementiert und werden in Zukunft auch noch weiter angereichert werden.

Um hier als Software-Entwickler den Überblick zu behalten – aktuell sind in TISS bereits über 140 Models vorhanden – haben wir ein Modul namens „Datenstruktur-Browser“ implementiert. Wie der Name schon sagt, kann man sich damit durch die Datenstruktur „durchklicken“. Man kann sich so auf ganz einfachem Weg ansehen, welche Tabellenspalten ein Model hat, welche Relationen zu anderen Models bestehen und welche Callback-Methoden verwendet werden.

In der Administrator-Version ist über diesen Weg auch ein direkter Zugriff auf die vorhandenen Daten möglich.

In einer Zwischen-Release wird der Datenstruktur-Browser in eingeschränkter Form für die TU-Mitarbeiter freigegeben werden. Das soll in erster Linie dazu dienen, Wissbegierigen einen kleinen Einblick in die Grundzüge des Systems zu geben. Außerdem kann er auch eine wertvolle Unterstützung für die Benutzer sein, die gewisse Berechtigungen für diverse Datenauswertungen erhalten.

Die untenstehenden Beispiele zeigen die Models „Mitarbeiter“ und „Dienstverhaeltnis“, die zueinander in einer 1:n-Beziehung stehen.