Rapid Prototyping einer Web-Anwendung mit Ruby on Rails
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
Westfälische Wilhelms-Universität Münster Ausarbeitung Rapid Prototyping einer Web-Anwendung mit Ruby on Rails im Rahmen des Seminars „Software Engineering“ im Wintersemester 2008/2009 Thomas Jansing Themensteller: Prof. Dr. Herbert Kuchen Betreuer: Dipl.-Wirt. Inform. Christian Hermanns Institut für Wirtschaftsinformatik Praktische Informatik in der Wirtschaft
Inhaltsverzeichnis 1 Einleitung ................................................................................................................... 1 2 Grundlagen von Ruby on Rails.................................................................................. 2 2.1 Allgemeine Konzepte und Ziele......................................................................... 2 2.2 Die Skriptsprache Ruby ..................................................................................... 3 2.3 Das Web-Framework Rails ................................................................................ 3 2.3.1 Eigenschaften und Besonderheiten ............................................................. 3 2.3.2 Aufbau des Frameworks ............................................................................. 5 2.3.3 RESTful Web-Services ............................................................................... 6 2.4 Eingesetzte Entwicklungsumgebung ................................................................. 6 2.5 Entwurf der Anforderungen ............................................................................... 7 3 Rapid Prototyping mit Ruby on Rails ........................................................................ 8 3.1 Vorstellung der Web-Anwendung ..................................................................... 8 3.2 Erstellung des Rails Projekts .............................................................................. 8 3.3 Testen der Funktionalität.................................................................................. 11 3.4 Web-Services und Routing .............................................................................. 12 4 Erweiterung des Projektes ....................................................................................... 14 4.1 Session-Handling und Benutzerverwaltung ..................................................... 14 4.2 Validierung in Modellen .................................................................................. 16 4.3 Templates und Layout ...................................................................................... 17 4.4 AJAX und Web 2.0 .......................................................................................... 18 4.5 E-Mail Versand durch ActionMailer ............................................................... 19 4.6 Sicherheitsaspekte ............................................................................................ 20 5 Zusammenfassende Betrachtung ............................................................................. 22 A Quelltexte ................................................................................................................. 23 Literaturverzeichnis ........................................................................................................ 38 II
Kapitel 1: Einleitung 1 Einleitung Die vorliegende Ausarbeitung soll eine Einführung in das Web-Framework Rails geben, welches auch synonym als Ruby on Rails bezeichnet wird, da es auf der Skriptsprache Ruby aufbaut. Anhand der Entwicklung einer Beispiel-Web-Anwendung sollen die we- sentlichen Elemente, sowie deren Vor- und Nachteile vorgestellt werden. Als Beispiel hierfür wurde ein Pizza-Bestell-Service ausgewählt. Die Fokussierung auf Ruby on Rails wurde gewählt, da es sich um ein relativ neues Framework handelt, welches durch die Kombination verschiedener Ansätze versucht das „Agile Web Development“ und speziell das „Rapid Prototyping“ zu unterstützen. Obwohl Ruby on Rails erst 2005 ver- öffentlicht wurde, wird es bereits in vielen produktiven Umgebungen (z. B. XING.com, Qype.com und Eins.de) eingesetzt und erfreut sich nicht nur aufgrund seiner intuitiven Syntax großen Interesses. Dank der Datenbank- und Plattform-Unabhängigkeit und der Kompatibilität zum Apache- und lighttpd Webserver gewährleistet Ruby on Rails Inter- operabilität für alle relevanten Web-Umgebungen. Die zugrunde liegende Sprache Ruby ist sowohl Programmier- wie auch Skriptsprache und kann daher die Vorteile von eher prozeduralen/imperativen Skriptsprachen wie dem weit verbreiteten PHP nutzen und dabei trotzdem die Vorteile der Objektorientierung nutzen, da jedes Element in Ruby ein Objekt ist. Ruby positioniert sich technologisch zwischen PHP/Perl auf der einen Seite und Java und C# auf der anderen. Demnach ist Ruby ist vergleichbar mit Python. Das folgende Kapitel 2 erläutert die Grundlagen von Ruby on Rails, wie die allgemei- nen Konzepte und Ziele (Kap. 2.1), die Sprache Ruby (Kap. 2.2) und das Framework Rails (Kap. 2.3) genauer. Kapitel 2.4 beschreibt die eingesetzte Entwicklungsumge- bung, woraufhin in Kapitel 2.5 schließlich die Anforderungen an die Beispiel-Web- Anwendung kurz erläutert werden. Kapitel 3 beschäftigt sich mit dem Thema „Rapid Prototyping“ und beginnt nach einer kurzen Beschreibung der Anwendung (Kap. 3.1) mit der Erstellung des Beispiel-Projektes (Kap. 3.2). Nachdem die Funktionalität in Kapitel 3.3 gezeigt wurde, wird direkt die einfache Bereitstellung als Web-Service de- monstriert (Kap. 3.4). Der erstellte Prototyp wird in Kapitel 4 um Funktionen erweitert, wie ein Login-System (Kap. 4.1), Validierung (Kap. 4.2), angepasste Layouts und Templates (Kap. 4.3), sowie eine AJAX-Live-Suche (Kap. 4.4). Das Kapitel schließt mit der Betrachtung einiger Sicherheitsaspekte in Kapitel 4.6. Die Ausarbeitung endet mit einer kritischen Zusammenfassung in Kapitel 5. 1
Kapitel 2: Grundlagen von Ruby on Rails 2 Grundlagen von Ruby on Rails 2.1 Allgemeine Konzepte und Ziele Das Web-Framework Ruby on Rails wurde speziell darauf ausgerichtet, den Program- mierer beim Rapid Development und im speziellen beim Rapid Prototyping zu unters- tützen. Kapitel 3 wird dies exemplarisch zeigen. Ziele des Rapid Development sind u. a. die Entwicklungsdauer und die dabei entstehenden Kosten zu senken. Die Programmie- rer können sich auf die Implementierung der Geschäftslogik konzentrieren und den Nutzern schneller ein lauffähiges System demonstrieren. Dieses soll durch kürzere Ent- wicklungs-Zyklen erreicht werden und dafür sorgen, dass das Feedback der Nutzer schneller in die Entwicklung einfließt. Dieser Ansatz verspricht mehr Agilität und Fle- xibilität bei der Entwicklung von Web-Anwendungen. Ruby on Rails unterstützt somit das Extreme Programming (XP), indem es erlaubt die gesamte Web-Anwendung in kleinen, iterativen Schritten zu entwickeln. So sind jederzeit Änderungen am Datenmo- dell möglich, ohne die Lauffähigkeit der Anwendung zu gefährden [WK07, S. 29]. Weiterhin unterstützt Ruby on Rails Test-driven development (TDD), indem es neben dem lauffähigen Prototyp auch Tests für Modellklassen, Kontroller und Views, sowie eine Fixture-Datei mit Testdaten generiert. In [WB06, Kap. 14] wird dieses ausführli- cher besprochen. Beim Anlegen neuer Kontroller werden z. B. automatisch funktionale Tests generiert, so dass bereits vor der ersten Anpassung des Codes die Funktionalität getestet werden kann [WK07, S. 30]. Hierdurch kann mit dem Testen begonnen werden, bevor der erste eigene Code implementiert wird. Ein wichtiger Vorteil der hieraus er- wächst ist die Möglichkeit durch das Testen ganz genau das gewünschte Verhalten der Anwendung zu spezifizieren und zu verifizieren [WK07, Kap. 4.3.1]. Ruby on Rails folgt strikt dem Model-View-Controller (MVC) Architekturmuster, wel- ches 1979 von Trygve Reenskaug vorgestellt wurde. Dieses Muster hat sich seit langem bewährt und verlangt eine strikte Trennung des Datenmodels (Model), der Präsentati- onsschicht (View) und der Steuerungsschicht (Controller) voneinander. Die Ziele hier- bei sind die Web-Anwendung flexibel zu gestalten, um Wiederverwendung zur ermög- lichen und einen möglichst geringen Pflegeaufwand zu haben, was zu einer geprüften und stabilen Software beitragen soll [Wa08, Kap. 4.3]. Die genaue Umsetzung des MVC-Musters in Rails wird in Kapitel 2.3.2 näher vorgestellt. 2
Kapitel 2: Grundlagen von Ruby on Rails 2.2 Die Skriptsprache Ruby Die Open-Source Programmiersprache „Ruby“ wurde 1995 von dem Japaner Yukihiro Matsumoto entwickelt und wird bis heute von ihm betreut. Ruby ist eine moderne Ob- jekt-orientierte Skriptsprache und bietet daher Vorteile wie z. B. Modularisie- rung/Kapselung von Objekten, Vererbung/Spezialisierung und Polymorphismus. Im Gegensatz zu anderen rein Objekt-orientierten Programmiersprachen wie z. B. Smaltalk bietet Ruby darüber hinaus auch Unterstützung für Prozedurale und Funktionale Prog- rammierung. So ist es bspw. nicht notwendig seine Programme explizit in einer Klasse zu definieren. Ein Ruby-Programm kann auch lediglich aus Prozeduren oder Funktionen bestehen. Sie unterstützt die dynamische Typisierung während der Laufzeit (Duck- Typing) und folgt dem sogenannten Principle of least surprise, d. h. dass die Program- miersprache den Programmierer möglichst wenig überraschen sollte und sie intuitiv verstanden werden sollte. Maßstäbe hierfür waren Matsumoto´s eigene Erwartungen und Bedürfnisse. Ein positiver Aspekt von Ruby ist daher die gute Lesbarkeit des Co- des. Ruby-Syntax ist gut verständlich, da die Programmiersprache sehr an die natürliche menschliche Sprache angelehnt ist. Beispiele hierfür werden in Kap. 3 gegeben. Als ausführliches Nachschlagewerk und Referenz für Ruby sei auf [CR2006] verwiesen. 2.3 Das Web-Framework Rails 2.3.1 Eigenschaften und Besonderheiten Rails ist ein Open-Source Projekt und steht unter der MIT-Lizenz [Op08] zur Verfü- gung. Sie erlaubt eine absolut freie Verwendung von Rails. Das Framework wurde von David Heinemeier Hansson entwickelt und, nachdem es 2004 bereits vorgestellt wurde, Ende 2005 in der Version 1.0 freigegeben. Auffällig hierbei ist die Praxisnähe von Rails, da es nicht als theoretisches Framework von Grund auf neu konzipiert wurde, sondern aus den Erfahrungen, Mustern und Teilen der Implementierung der Projektma- nagement-Software Basecamp zusammengestellt wurde. Daher berücksichtigt es konk- rete Lösungen und erfolgreiche Muster aus einem Praxisprojekt, was wohl auch zu der schnellen Verbreitung von Rails beiträgt [WK07, S. 21]. Eine Besonderheit von Rails ist sicherlich das Paradigma Konvention über Konfigurati- on, welches immer wieder am laufenden Fall-Beispiel verdeutlicht wird. Rails erwartet 3
Kapitel 2: Grundlagen von Ruby on Rails vom Entwickler, dass er sich an Namenskonventionen hält, die eine spätere Konfigura- tion überflüssig machen. Beispielhaft sei aufgeführt, dass Klassennamen immer im Sin- gular erwartet werden, während die Tabellennamen den Pluralnamen tragen. Sämtliche generierten Dateien folgen diesem Schema. Rails bietet aber auch die Möglichkeit von den Default-Einstellungen abzuweichen [WB06, S. 7 f.; WK07, S.28 f.]. Ein zweites Paradigma von Rails ist das DRY-Prinzip, welches von [TH04] geprägt wurde. DRY steht für Don´t repeat yourself und baut auf dem Grundsatz auf, dass Wis- sen jeweils nur eine einzige und eindeutige Repräsentation in einem Informationssystem haben sollte. Daher sollen weder Daten noch Funktionen redundant gespeichert werden, um den Pflegeaufwand zu reduzieren und mögliche Probleme zu vermeiden. Ein Bei- spiel hierfür sind die Getter- und Setter-Methoden, welche nicht implementiert werden müssen, da sie gleichartig sind und daher generiert werden können [Vgl. WB06, S. 8]. Besonders erwähnenswert ist die eigentliche Stärke von Rails, die Meta Programmie- rung. Die automatische Generierung von Programmcode wird bei Ruby on Rails Scaf- folding genannt. Mit Hilfe von Scaffold-Generatoren wird direkt zu Beginn ein lauffä- higes Gerüst erzeugt, welches bereits grundlegende Funktionen zum Erstellen, Anzei- gen, Updaten und Löschen von Modellen (CRUD-Funktionen) implementiert. Zusätz- lich werden auch die Views erzeugt, die eine Webbrowser-basierte Umsetzung der CRUD-Funktionen ermöglichen. Somit kann sofort ein lauffähiger Prototyp erzeugt werden, welcher als Grundlage für die weitere Entwicklung dient (Rapid Prototyping). Ein Vorteil von Rails ist die Integration und besonders einfache Nutzung des Objektre- lationen Mappings (ORM). Im Gegensatz zu anderen Frameworks wie z. B. MyFaces ist bei Rails nur sehr wenig Aufwand hierfür nötig, da durch die Namenskonventionen eine Konfiguration überflüssig ist. Nur Assoziationen zwischen Objekten müssen (noch) manuell implementiert werden. Im Zusammenhang mit dem ORM sind die Da- tenbank-Migrationsskripte zu nennen. Diese automatisch generierten Skripte gewähr- leisten eine Kompatibilität zu allen verbreiteten Datenbanken und gestatten somit den Betrieb von Rails mit einer Vielzahl an Datenbanken. Die Migrationen bieten darüber hinaus den Vorteil, dass eine Versionierung von Datenbank-Schemata möglich ist. Es können neue Schemata migriert oder auch auf ältere zurückgekehrt werden. Rails bietet drei verschiedene Umgebungen mit jeweils eigenen Datenbanken. Standard ist die Development-Umgebung, wohingegen die Production-Umgebung für den Pro- duktiv-Einsatz vorgesehen ist. Alle Tests werden in der Test-Umgebung mit Test-Daten 4
Kapitel 2: Grundlagen von Ruby on Rails ausgeführt. Diese Trennung der Daten erlaubt bspw. das Testen von Änderungen am Datenbank-Schema, ohne auf Daten der Produktiv-Umgebung zuzugreifen und diese möglicherweise zu verändern oder zu löschen. 2.3.2 Aufbau des Frameworks Abbildung 1 zeigt die Komponenten von Rails und deren Interaktion sowie die konkrete Umsetzung des MVC-Musters. Der Ruby-Dispatcher im Zentrum erhält vom Web- Server die Anfrage des Clients. Der Dispatcher lädt daraufhin anhand des Routings (config\routes.rb) den entsprechenden Controller des ActionController Moduls. Der Controller verwaltet die Anfrage und ruft die entsprechenden Methoden des Acti- veRecord oder ActiveRessource Moduls auf. ActiveRecord interagiert mit der Daten- bank als OR-Mapper und enthält die Logik zur Verwaltung der Daten. ActiveRessource (ab Rails Version 2.0) hingegen ist für RESTful Web-Service-Anfragen und nutzt Acti- veRecord als ORM. Die Ergebnisse der Aufrufe werden an den Controller zurückge- schickt und dann vom Controller an das ActionView Modul gesendet. Das ActionView Modul rendert die Ergebnisse und liefert sie an den Client zurück. Abbildung 1: Komponenten von Rails (ab Version 2.x) Zusätzlich können ActionController, ActiveRecord und -Ressource auf ActiveSupport zugreifen, welches Erweiterungen und Hilfsmethoden für Rails implementiert. Daneben 5
Kapitel 2: Grundlagen von Ruby on Rails gibt es das ActionMailer Modul, welches den E-Mail-Versand übernimmt. Alle Action Module werden auch unter dem Begriff ActionPack zusammengefasst. 2.3.3 RESTful Web-Services Das Representational State Transfer (REST) Architektur-Muster wurde von [Fi00] für verteilte Hypermedia-Systeme, wie z. B. das World Wide Web, beschrieben und wird ausführlich in [Fi00, Kap. 5] behandelt. In den Versionen 1.x von Rails wurde das Mo- dul Action Web Service als Standard für die Bereitstellung von Web-Services vorgese- hen. Das Modul bietet Unterstützung für SOAP- und XML-RPC-Zugriffe. Mit der Rails Version 2.0 wurde das Modul durch ActiveRessource ersetzt, welches Web-Services über REST unterstützt. Eine ausführliche Behandlung des Themas ist in [RR07] nach- zulesen. Action Web Service kann über RubyGems nachinstalliert werden, aber die Rails Entwickler empfehlen SOAP nicht mehr einzusetzen und REST zu nutzen. Rails 2.0 ist bereits RESTful konzipiert, was bereits beim Prototyping deutlich wird. 2.4 Eingesetzte Entwicklungsumgebung Für die Entwicklung der Beispiel-Web-Anwendung wurde als Betriebssystem Windows XP benutzt, so dass bei allen folgenden Befehlen in der Eingabeaufforderung ein „\“ anstelle eines „/“ als Pfadtrenner benutzt wurde. Entsprechend der Empfehlung der rubyonrails.com Webseite wurde das Ruby-Paket in der Version 1.8.6-27 RC 1 [Ruby08a] verwendet. Nach erfolgter Installation von Ruby („RubyGems Support“ und „European Keyboard“ müssen aktiviert werden) wurde mit Hilfe der mitgelieferten Ru- byGems Paket-Verwaltung das Rails-Paket (Ver. 2.1.1), der SQLite3 Adapter (Ver. 1.2.3) und der Mongrel Web-Server (Ver. 1.1.5) heruntergeladen: “gem install rails --version 2.1.1” “gem install sqlite3-ruby --version 1.2.3” “gem install mongrel --version 1.1.5” Die vorliegende Rails Version besitzt leider eine bekannte Inkompatibilität unter Win- dows XP mit allen MySQL-Servern, daher wurde SQLite3 als Datenbank benutzt. Um SQLite3 zu installieren wurden die „Precompiled binaries“ für Windows heruntergela- den (sqlite-3_6_4.zip und sqlitedll-3_6_4.zip [Sqlite08]) und anschließend in das \bin Verzeichnis der Ruby-Installation extrahiert, damit sie über die PATH- Variable in allen Verzeichnissen ausführbar sind. Für die Integration anderer Datenban- 6
Kapitel 2: Grundlagen von Ruby on Rails ken wird jeweils ein Adapter benötigt, welcher auch in der DB-Konfiguration des Pro- jekts angegeben werden muss (config\database.yml). Ausführliche Hinweise zur Installation und Konfiguration von Datenbanken bietet das offizielle Ruby on Rails- Wiki [Ruby08b]. 2.5 Entwurf der Anforderungen Mit Fokus auf das Extreme Programming wird hier auf eine Analyse- und Spezifikati- ons-Phase sowie auf ein Pflichten- und Lastenheft im Sinne des klassischen Software Engineering verzichtet. Es wird direkt mit einer textuellen Formulierung der Anforde- rungen begonnen, auf deren Basis ein UML-Diagramm (Vgl. Abbildung 2) erstellt wird. Das Projekt pizza-service soll einen Pizza-Bestellservice implementieren. Es soll eine Pizza-Verwaltung (Klasse: Pizza) für einen Admin (Klasse: User), sowie eine grundlegende Bestell-Funktionalität (Klasse: Order) für alle Kunden (Klasse: User) ers- tellt werden. Kunden sollen sich nach einer Registrierung einloggen können, um nicht zu jeder Bestellung nochmals ihre Adresse angeben zu müssen. Außerdem können sie ihr Profil jederzeit ändern. Ein Admin soll sich über einen Login autorisieren und die Pizzen verwalten können. Bestellungen von Kunden werden per E-Mail an den Liefer- service gesendet. Weiterhin soll eine Suchfunktion implementiert werden, um nach Piz- zen mit bestimmten Zutaten zu suchen. Diese Suchfunktion wird einerseits als RESTful WebService und zusätzlich als AJAX-Live-Suche implementiert. Abbildung 2: Vereinfachtes UML-Diagramm der Beispiel-Web-Anwendung Einem User können mehrere Bestellungen zugeordnet werden. Eine Bestellung ihrer- seits wird genau einem User zugeordnet und kann aus mehreren Pizzen bestehen. Pizzen können in mehreren Bestellungen enthalten sein. Diese n:m Beziehung wird hierbei durch die Assoziationsklasse Orders_Pizzas abgebildet. Get- und Set-Methoden, sowie IDs und Timestamps sind nicht angegeben, da sie automatisch generiert werden. Die Attribute werden als SQLTypes definiert. 7
Kapitel 3: Rapid Prototyping mit Ruby on Rails 3 Rapid Prototyping mit Ruby on Rails 3.1 Vorstellung der Web-Anwendung Im Rahmen dieses Kapitels wird das Rapid Prototyping mit Rails verdeutlicht. In die- sem Kontext werden Klassen synonym als Modelle und Methoden als Aktionen be- zeichnet. In einem ersten Schritt sollen die Modelle erstellt und deren Verwaltung (CRUD-Operationen) in Views ermöglicht werden. Außerdem sollen die Assoziationen der Modelle berücksichtigt werden. Die Funktionalität wird anhand der Ruby-Konsole überprüft. Anschließend wird eine einfache Suchfunktion als WebService implementiert (Kapitel 3). Als zweiter Schritt soll der Prototyp um eine Benutzerverwaltung erweitert werden, um die Bestellung für registrierte Kunden zu vereinfachen und nur einem Ad- min die Verwaltung von Pizzen zu gestatten. Nachdem die Validierung und das Arbei- ten mit Templates gezeigt werden, werden die AJAX-Live-Suche und ein automatischer E-Mail-Versand implementiert und Sicherheitsaspekte besprochen (Kapitel. 4). Da sich die Ausarbeitung auf die Umsetzung der Anwendung konzentriert, wird hier teilweise auf eine komplette Auflistung der Änderungen an den View-Templates verzichtet und auf den Anhang verwiesen. 3.2 Erstellung des Rails Projekts Nachdem die Entwicklungsumgebung in Kapitel 2.4 eingerichtet wurde, wird als erster Schritt das neue Rails Projekt pizza-service erstellt. Dieses geschieht über folgen- den Befehl in der Eingabeaufforderung in einem beliebigen Verzeichnis: “rails pizza-service” “cd pizza-service” Rails erstellt automatisch im Verzeichnis pizza-service eine festgelegte Verzeich- nisstruktur (Vgl. Tabelle 1) und erzeugt alle benötigten Dateien, wodurch der grundsätz- liche Aufbau der Web-Anwendung bereits vorgegeben ist. Dieses reduziert den Einar- beitungsaufwand bei zukünftigen Projekten. Jede Rails-Anwendung verwendet die glei- che Struktur, wodurch eine spätere Konfiguration, wie im J2EE-Bereich üblich, vermie- den wird. Hierbei wird nochmal das Prinzip „Konvention statt Konfiguration“ deutlich. Verzeichnis Inhalt app Alle wichtigen Bestandteile des MVC-Musters: Hilfs-, Kontroller- 8
Kapitel 3: Rapid Prototyping mit Ruby on Rails und Modell-Klassen, sowie sämtliche Views (*.html.erb) config Konfigurationsdateien (z. B. database.yml) db DB-Migrationsskripte, SQLite3 Datenbank-Datei (nach Anlegen) doc Dokumente, die mit rdocs erstellt wurden lib Zusätzliche Ruby-Bibliotheken/Erweiterungen log Logfiles des WEBrick/Mongrel-Servers public Öffentliches Wurzel-Verzeichnis des Web-Servers mit statischen Da- teien (z. B. Bilder, JavaScript, CSS, ...) script Start-Skripte (z. B. Server-Start, DB-Konsole, Generatoren) test Test-Skripte, Unit-Tests, Funktionale Tests sowie Integrationstests tmp Temporäre Dateien (z. B. Session-Dateien, Cache-Dateien) vendor Plug-Ins und andere Erweiterungen Tabelle 1: Verzeichnisstruktur einer Rails-Anwendung Nun wird das UML-Diagramm (Vgl. Abbildung 2) mit Hilfe des Scaffold-Generators umgesetzt. Die drei Modelle Pizza, Order und User werden erstellt, wobei Rails au- tomatisch sämtliche Controller, Views, Tests und DB-Migrationsskripte erzeugt. Die Assoziationsklasse Orders_Pizzas wird nicht als Modell umgesetzt, sondern später nur als SQL-Tabelle. Die Attribute werden aus dem UML-Diagramm übernommen: “ruby script\generate scaffold pizza name:string price:decimal ingredients:string” “ruby script\generate scaffold order delivery_wish:text” “ruby script\generate scaffold user name:string address:string zip_code:integer city:string login:string password:string” In der eingesetzten Rails-Version ist es (noch) nicht möglich, Assoziationen automa- tisch zu berücksichtigen. Dazu werden die leeren generierten Klassen wie folgt ergänzt: class Pizza < ActiveRecord::Base # Einer Pizza werden 0..* Bestellungen zugeordnet has_and_belongs_to_many :order end Listing 1: app\models\pizza.rb class Order < ActiveRecord::Base # Einer Bestellung werden 0..* Pizzen zugeordnet has_and_belongs_to_many :pizza # Eine Bestellung wird genau einer Person zugeordnet belongs_to :user end Listing 2: app\models\order.rb class User < ActiveRecord::Base # Einem User werden 0..* Bestellungen zugeordnet has_many :order end Listing 3: app\models\user.rb 9
Kapitel 3: Rapid Prototyping mit Ruby on Rails Als nächster Schritt muss das OR-Mapping konfiguriert werden. Durch den Scaffold- Generator sind bereits die DB-Migrationsskripte (db\migrate\*.rb) erstellt worden und beinhalten bereits alle oben angegeben Attribute. Daher müssen sie nur noch an die Assoziationen angepasst werden. Um die Zuordnung der Bestellung zu genau einem User zu ermöglichen, wird deren Primärschlüssel als Fremdschlüssel in der orders- Tabelle gespeichert. Um die many-to-many-Beziehung zwischen Bestellung und Pizza abbilden zu können, muss eine neue Tabelle orders_pizzas angelegt werden. Diese Tabelle braucht keinen eigenen Primärschlüssel und beinhaltet nur die beiden Fremd- schlüssel, um die Relation abzubilden. Als Konvention für den Namen einer zusätzli- chen Tabelle einer many-to-many-Beziehung gilt, dass beide pluralisierten Tabellen- namen alphabetisch angeordnet und durch einen Unterstrich verbunden werden. class CreateOrders < ActiveRecord::Migration def self.up # Neues Schemata wird migriert create_table :orders do |t| t.text :delivery_wish t.integer :user_id # Fremdschlüssel wird ergänzt t.timestamps end # Hilfstabelle wird angelegt. Keine eigene id. create_table :orders_pizzas, :id => false do |t| t.integer :order_id # Fremdschlüssel der Bestellungen t.integer :pizza_id # Fremdschlüssel der Pizzen end end def self.down # Alte Tabellen/Schemata werden gelöscht drop_table :orders drop_table :orders_pizzas # Hilfstabelle entfernen end end Listing 4: db\migrate\timestamp_create_orders.rb Da die anderen Migrationen nicht geändert werden, kann jetzt die Datenbank aktuali- siert werden. Weil SQLite 3 als Datenbank benutzt wird, muss keine Änderung an der Datei config\database.yml vorgenommen werden. Mit dem Rake-Tool können vordefinierte Aufgaben, wie z. B. die Datenbank-Migration ausgeführt werden. Eine komplette Übersicht ist unter [MO08, Kap. 7.11] zu finden. Rake ist daher mit den Tools ant bzw. make zu vergleichen [St08, S. 138]. Folgender Befehl migriert das Schemata in die Datenbank: “rake db:migrate” Die Development-Datenbank-Datei db\development.sqlite3 wird erstellt und vier neue Tabellen werden angelegt. 10
Kapitel 3: Rapid Prototyping mit Ruby on Rails 3.3 Testen der Funktionalität Für einen direkten Test der Funktionalität werden die Ruby-Konsole und die Views benutzt. Für das Testen mit Hilfe von Testfällen und Testskripten sei hier auf [WB06, Kap. 14] verwiesen. Als Webserver für die Entwicklung kann der integrierte WEBrick- Server verwendet werden. Eine bessere Performance bietet Mongrel, welcher in Pro- duktiv-Umgebungen eingesetzt wird. Das Server-Start-Skript überprüft ob Mongrel installiert ist und startet standardmäßig diesen (andernfalls wird WEBrick gestartet): “ruby script\server” Unter der URL http://localhost:3000 kann die Startseite des Web-Servers er- reicht werden. Da Ruby on Rails als CRUD-Framework ausgelegt ist, sind durch das Scaffolding bereits alle nötigen Views und die gesamte Logik vorhanden, um die drei Modelle zu verwalten. Die Index-Seite für deren Verwaltung ist nach folgendem Sche- ma aufgebaut: http://localhost:3000/modelname_im_plural. Als hilfreich zum Testen der der generierten Namen hat sich der Pluralizer [Nu08] erwiesen. Es ist jeweils möglich Instanzen der Modelle anzulegen, anzuzeigen, zu editieren und zu lö- schen. Über die Ruby-Konsole werden Instanzen von Modellen angelegt, die durch Zu- weisungen miteinander assoziiert werden, um die Funktionalität der Assoziationen zu demonstrieren. Zuerst werden über die Konsole drei Pizzen und zwei Kunden angelegt: “ruby script\console” pizza1 = Pizza.new(:name => "Pizza Margherita", :price => 4.95, :ingredients => "Tomaten, Kaese") pizza2 = Pizza.new(:name => "Pizza Salami", :price => 5.45, :ingredients => "Tomaten, Kaese, Salami") pizza3 = Pizza.new(:name => "Pizza Tono", :price => 5.95, :ingredients => "Tomaten, Kaese, Thunfisch") user1 = User.new(:name => "Max Mustermann", :address => "Han- saring 55", :zip_code => 48143, :city => "Muenster") user2 = User.new(:name => "Peter Muster", :address => "Wolbeck- er Str. 55", :zip_code => 48143, :city => "Muenster") pizza1.save pizza2.save pizza3.save user1.save user2.save Listing 5: Eingaben in der Ruby-Konsole (1/3) Eine Kontrolle im Webbrowser zeigt, dass alle Instanzen angelegt wurden. Nun wird eine Bestellung erzeugt, die Kunde 1 zugeordnet wird und Pizza 2 & 3 enthält: 11
Kapitel 3: Rapid Prototyping mit Ruby on Rails order1 = Order.new(:delivery_wish => "Bitte noch 1 Liter Cola") user1.order
Kapitel 3: Rapid Prototyping mit Ruby on Rails GET und POST-Anfragen zulässt und PUT und DELETE blockiert. Die durch den Scaffold-Generator erzeugten Controller implementieren RESTful Web-Services bereits über das Routing. Die Datei config\routes.rb ist dabei zentral für das Routing, welches für einen HTTP-Request den verantwortlichen Controller und die auszuführen- de Aktion auswählt. Die Bereitstellung als Ressource ist bereits über den Eintrag (map.resources :pizzas) implementiert. Der Aufruf (http://localhost:3000/pizzas/edit/1) wird wie (PUT http://localhost:3000/pizzas/1) an den PizzasController weitergeleitet und ruft die Aktion edit für das Objekt mit der ID 1 auf. Bei der Erstellung komple- xerer RESTful Web-Services sollte auf konsistente Adressierung geachtet werden. Das Projekt wird nun um einen RESTful Web-Service search_all erweitert, der die Zuta- ten-Spalte der Pizza-Tabelle durchsucht und im PizzasController implementiert wird. Gleichzeitig wird das Routing um den neuen Web-Service ergänzt: # GET /pizzas/search_all # GET /pizzas/search_all.xml def search_all #Suchbegriff wird für die SQL Abfrage mit % geklammert @term = "%#{params[:term]}%" #Suchanfrage wird ausgeführt. SQL-Injection wird vermieden. @pizzas = Pizza.find(:all, :conditions => ["ingredients like ?", @term ]) #Rendert das Ergebnis nur als XML, kein HTML, kein AJAX respond_to do |format| format.xml { render :xml => @pizzas } end end Listing 8: Neue Methode in: app\controllers\pizzas_controller.rb # “map.ressources :pizzas” wird geändert in: map.resources :pizzas, :collection => {:search_all => :get} Listing 9: Erweiterung von: config\routes.rb Folgende GET-Anfrage eines Webbrowsers bspw. liefert eine XML-Liste aller Pizzen, die „Salami“ als Zutat haben (Web-Server muss neu gestartet werden): “http://localhost:3000/pizzas/search_all?term=salami” SQL-Injection (Vgl. Kap. 4.6) ist hierbei nicht möglich, da die Binding-Funktionalität benutzt wird. Da REST-Anfragen stark sicherheitsrelevant sind, ist eine ausführliche Beschäftigung mit [Ruby08d] anzuraten. 13
Kapitel 4: Erweiterung des Projektes 4 Erweiterung des Projektes 4.1 Session-Handling und Benutzerverwaltung Mit Hilfe der Attribute login und password des User-Modells wird nun eine einfa- che Benutzerverwaltung implementiert. Weiterhin wird dazu der Session-Hash und Flash-Hash benutzt. Der Session-Hash speichert Objekte über mehrere Requests hin- weg, und wird als Speicher für den angemeldeten User (bzw. User-Objekt) benutzt. Auf die Objekte wird hierbei mit der session Variable zugegriffen. Der Flash-Hash hin- gegen speichert Objekte hingegen nur für zwei aufeinander folgende Requests und wird für Fehlermeldungen sowie Hinweise und Warnungen verwendet. Die Informationen werden hierbei in der flash Variable gespeichert [St08, S.180]. Für die Benutzerverwaltung (Vgl. [St08, Kap. 4.6.3]) wird ein neuer LoginControl- ler mit den Aktionen login und logout generiert und angepasst: “ruby script\generate controller Login login logout” class LoginController < ApplicationController def login session[:user] = nil # User-Objekt und Werte löschen session[:user_id] = nil session[:user_login] = nil # Login-Button löst POST-Request aus if request.post? # Authentifiziere User -> app\models\user.rb user = User.authenticate(params[:login], params[:password]) # Konnte User-Objekt erfolgreich geladen werden? if user != nil # Wenn ja setze neues Objekt und Werte session[:user] = user session[:user_id] = user.id session[:user_login] = user.login # Hinweis und Redirect flash[:notice] = "Successfully logged in!" redirect_to :controller => "pizzas", :action => "index" else # Fehler bei der Authentifizierung flash[:error] = "Login or passwort are incorrect!" end end end def logout reset_session # Löscht alle Session Objekte des Users end end Listing 10: app\controllers\login_controller.rb 14
Kapitel 4: Erweiterung des Projektes Die eigentliche Authentifizierung soll im Modell geschehen. Daher wird das User- Modell um die Methode authenticate erweitert, die das User-Objekt anhand des login Parameters lädt und das übergebene Passwort mit dem gespeicherten vergleicht: def self.authenticate(login, password) user = self.find_by_login(login) # Lade User-Objekt if user # User-Objekt erfolgreich geladen, prüfe Passwort if user.password != password user = nil # Passwort falsch. Gib Fehler zurück (NIL) end end user # Passwort richtig. Gib User-Objekt zurück end Listing 11: Neue Methode in: app\models\user.rb Nun kann der Zugriff auf die einzelnen Aktionen der Controller geschützt werden. Dazu werden die Controller um Filter erweitert, die vor (before_filter) oder nach (af- ter_filter) jeder Aktion ausgeführt werden: # Delete ist admin-geschützt, Anzeigen und Anlegen nicht before_filter :admin_logged_in, :only => [:delete, :destroy] # User dürfen nur ihre eigenen Profile ändern before_filter :logged_in, :own_profile,:only =>[:edit, :update] Listing 12: Erweiterung von: app\controllers\users_controller.rb # Delete/Edit sind admin-geschützt, Anzeigen nicht before_filter :admin_logged_in, :only => [:delete, :destroy, :edit, :update] # Um Bestellungen anzulegen muss User eingeloggt sein before_filter :logged_in, :only => [:create, :new] Listing 13: Erweiterung von: app\controllers\orders_controller.rb # Create/Edit/Delete sind admin-geschützt, Anzeigen und Suchen nicht before_filter :admin_logged_in, :except => [:index, :list, :show, :search_all] Listing 14: Erweiterung von: app\controllers\pizzas_controller.rb Die Methoden admin_logged_in, logged_in und own_profile werden im App- licationController als Private-Methoden implementiert, damit sie über die Verer- bung allen Controllern zur Verfügung stehen: private # Überprüft ob User eingeloggt ist def logged_in if session[:user] == nil flash[:error] = "You need to login first!" redirect_to(:action => 'index') end 15
Kapitel 4: Erweiterung des Projektes end private # Überprüft ob User sein eigenes Profil editiert def own_profile if session[:user].id.to_s != params[:id] flash[:error] = "You can only edit your own profile." redirect_to(:action => 'index') end end private def admin_logged_in # Überprüft ob user.login = admin logged_in if session[:user] != nil if session[:user].login.to_s == "admin" flash.now[:notice] = "You have admin rights." else flash[:error] = "You need to have admin rights to use this function." redirect_to(:action => 'index') end end end Listing 15: Neue Methoden in: app\controllers\application.rb Nachdem die generierten Views für den Login und den Logout angepasst wurden (Vgl. Anhang A), steht die Benutzerverwaltung zur Verfügung: “http://localhost:3000/pizzas” # Pizza-Service Startseite “http://localhost:3000/login/login” # Anmeldeseite “http://localhost:3000/login/logout” # Abmeldung “http://localhost:3000/users/new” # Registrierung neuer User 4.2 Validierung in Modellen Die Validierungsfunktionalität ist fester Bestandteil von ActiveRecord und findet somit auf Modellebene statt. Es kann entweder eine Klassen-Methode validate in die Mo- dell-Klasse eingefügt werden, um eigene Validierungen zu implementieren, oder auf Standard-Methoden von ActiveRecord zurückgegriffen werden, die als Klassen- Methoden eingefügt werden. Nachfolgend wird das Pizza-Modell um verschiedene Va- lidierungen erweitert: # Alle drei Attribute müssen angegeben werden validates_presence_of :name, :price, :ingredients # Das Attribut “Name” der Pizza soll eindeutig sein validates_uniqueness_of :name # Das Attribut “Preis” soll positiv sein validates_numericality_of :price, :on => :create, :greater_than => 0, :message => "Price must be > 0." Listing 16: Erweiterung von: app\models\pizza.rb 16
Kapitel 4: Erweiterung des Projektes Der Parameter :on definiert wann die Validierung durchgeführt werden soll (hier beim Anlegen) und :message überschreibt die eventuell ausgegebene Fehlermeldung (vor allem bei numerischen Prüfungen wichtig). Als Standard werden die Validationen bei jedem Ändern (:save) des Objekts ausgeführt. Standard-Fehlermeldungen sind bereits definiert. Die Modelle User und Order werden ebenfalls um sinnvolle Validationen erweitert (Vgl. Anhang A). Eine gute Übersicht und Beschreibung aller vorhandenen Standard-Validationen bieten [MR06, Kap. 5.6.6]. 4.3 Templates und Layout Rails verfügt über drei eingebaute Template-Engines, die Teil des ActionView-Moduls sind (ERb-, XML- und Ruby JavaScript-Templates). Anhand der Dateiendung erkennt Rails, welche Engine zu wählen ist. Ab Rails 2.0 werden als Standard ERB-Templates (app\views\*\*.html.erb) vom Scaffold-Generator erzeugt. Embedded Ruby (ERb)-Templates sind einfache HTML-Templates mit über Tags eingebettetem Ruby- Code. Tabelle 2 gibt hierüber einen Überblick (Vgl. [MO08, Kap. 8]). Tag Beschreibung Code wird ausgewertet, keine Ausgabe Code wird ausgewertet, keine Ausgabe, unterdrückt Code wird ausgewertet und als String ausgegeben Code wird ausgewertet, Resultat wird durch html_escape gefiltert (Vgl. Kap.4.6) Code wird nicht ausgewertet (Kommentar) Tabelle 2: Tags zum Einbinden von Ruby-Code in ERb-Templates Da nach dem MVC-Muster die Controller die Views steuern, muss zum Rendern der Views innerhalb einer Aktion eines Controllers die render-Funktion aufgerufen wer- den, der als Parameter der Name des Templates übergeben wird. Aufgrund der Namen- konvention wird Rails die Endung .html.erb hinzufügen. Allgemein: Render :action => „Template_name“ Bei :action stehen dem Controller nur Templates aus dem eigenen Unterordner zur Verfügung. Über :template kann ein relativer Pfad und über :file ein absoluter Pfad zu anderen Templates angegeben werden [MR06, S. 68]. Im Ordner app\views\layouts befinden sich spezielle Templates, die nach dem DRY-Prinzip als gemeinsames Layout für alle Seiten eines Controllers dienen (control- ler.hmtl.erb). Wird die Datei application.html.erb angelegt, dient diese als 17
Kapitel 4: Erweiterung des Projektes Layout für alle Seiten der Anwendung. Das Projekt wird um ein gemeinsames Layout erweitert, dass alle Funktionen und Seiten zusammenführt (Vgl. Anhang A). 4.4 AJAX und Web 2.0 „Asynchronous JavaScript and XML“ (AJAX)-Funktionalität ist fester Bestandteil von Ruby on Rails. Hierfür wurden die beiden AJAX-Frameworks Prototype und Script.aculo.us integriert. Eine ausführliche Darstellung von AJAX und den beiden Frameworks wird in [Ga07, Kap.6 & 7] gegeben. Die JavaScript Dateien befinden sich unter public\javascripts und werden in den Views wie folgt eingebunden: Dadurch werden alle relevanten JavaScript-Dateien geladen (Prototype, Script.aculo.us, sowie application.js, die selbst erstellte JavaScripts enthält). Diese Skripte können nun in den Views verwendet werden um AJAX zu realisieren, wodurch aber eine Ver- mischung von HTML, Ruby und JavaScript auftritt. Um diese zu vermeiden und ein- heitlich nur HTML und Ruby-Code zu schreiben, kann der Ruby JavaScript (RJS)- Generator benutzt werden. Dieser übersetzt Ruby-Code zur Laufzeit in JavaScript und ermöglicht eine einheitliche Programmierung in Ruby. Da JavaScript zum View im MCV-Muster gehört, ist es besser, den RJS-Code in Templates auszulagern, die den Namen der aufzurufenden Action und die Endung .js.rjs tragen. Nachdem die search_all Methode in Kap. 3.4 bereits als WebService über XML realisiert wurde, wird diese nun um einen HTML-View mit Live-Search-Funktion er- weitert, um die einfache Einbindung von AJAX zu demonstrieren: false, :partial => 'live_search' unless request.xhr? %> Search results Name Price Ingredients 18
Kapitel 4: Erweiterung des Projektes No matches found. Listing 17: app\views\pizzas\search_all.html.erb 'search_all'}, :method => 'get') do %> Live-Search query: 'loading', :style => 'display:none' %> {:action => 'search_all'}, :with => 'term', :frequency => 1, :update => 'live_results', :before => "Element.show('loading'); Ele- ment.hide('live_results')", :complete => "Ele- ment.hide('loading');" + visual_effect(:appear,:live_results )) %> Listing 18: app\views\pizzas\_live_search.html.erb def search_all [...] #Rendert das Ergebnis respond_to do |format| if request.xhr? # AJAX Anfrage? -> Layout nicht neu rendern render :layout => false and return end format.html # Rendert views\pizzas\search_all.html.erb # Falls XML-Anfrage rendere XML-Antwort format.xml { render :xml => @pizzas } # Rendert XML end end Listing 19: Aktualisierte Methode in: app\controllers\pizzas_controller.rb Das zentrale Element hierbei ist der Observer, der bei Änderungen im Suchfeld die Ak- tion search_all neu ausführt und den DIV-Container live_results aktualisiert. 4.5 E-Mail Versand durch ActionMailer Damit die Bestellungen auch Beachtung finden, werden sie nach ihrem erfolgreichen Anlegen mit Hilfe des ActionMailer-Moduls per E-Mail an den admin (bspw. tho- mas@jansing.de) versandt. Hierzu wird das Model order_mailer generiert: “ruby script\generate mailer order_mailer order_email” class OrderMailer < ActionMailer::Base # Erbt von ActionMailer def order_email(order, sent_at = Time.now) # Order als Param. 19
Kapitel 4: Erweiterung des Projektes @subject = 'New Pizza-Order' @recipients = 'thomas@jansing.de' @from = 'Pizza-Service' @sent_on = sent_at @body = {:order => order} # Order-Objekt wird übergeben end end Listing 20: app\models\order_mailer.rb respond_to do |format| if @order.save # Schicke Bestellung als Email OrderMailer.deliver_order_email(@order) [...] Listing 21: Erweiterung der Create-Methode in: app\controllers\orders_controller.rb Beim Ausführen der create-Methode in OrdersController wird die deli- ver_order_email-Methode von OrderMailer aufgerufen und ihr das Order-Objekt übergeben. Durch die Variable @body steht es im Template des E-Mail-Body über @order wieder zur Verfügung, so dass alle Werte der Bestellung eingetragen werden können. Das Template views\orders\order_email.erb befindet sich im Anhang A. Standardmäßig versendet ActionMailer E-Mails über einen SMTP-Server, dessen Zugangsdaten in der Datei config\environment.rb hinterlegt werden müssen [Ru- by08e]. Auch das Versenden über sendmail wird unterstützt. Zum Testen kann aber auch ohne Konfiguration das Versenden der E-Mail im Server-Log überprüft werden. 4.6 Sicherheitsaspekte Nach [Or07, Kap. 11] ist Sicherheit insbesondere bei Web-Anwendungen von großer Bedeutung, da diese über das Internet stets öffentlich zugänglich sind. Da der öffentli- che Zugang zu bestimmten Dateien auf dem Webserver zwingend notwendig ist, kann nicht verhindert werden, dass potentielle Angreifer versuchen die Web-Anwendung auf Schwachstellen und Sicherheitslücken zu untersuchen. Diese Attacken können auch automatisiert von Skripten durchgeführt werden und versuchen dabei bekannte Sicher- heitslücken auszunutzen. Die zwei größten sicherheitskritischen Bereiche sind einerseits „SQL-Injection“ sowie „cross-site scripting“ (XSS). Dazu ergänzen [WB06, Kap. 11.1] noch die für Ruby on Rails-Anwendungen kritischen Batch-Updates von Modellen, mögliche unsichere Dateiendownloads durch die send_file() Methode und die Di- rekteingabe einer URL mit ID. Grundsätzlich sollten alle möglichen Benutzer-Eingaben 20
Kapitel 4: Erweiterung des Projektes gefiltert und validiert werden und der Output auf unsichere und ungültige Zeichen und oder Informationen überprüft werden („escape output“). Zur Vermeidung von SQL-Injection sollte nach [WB06, S. 234] bei Datenbankabfragen in der Geschäftslogik nicht der Ruby-Ersetzungsmechanismus „#{code}“ benutzt werden, da dieser die Benutzereingaben ungeprüft in die SQL-Abfrage einsetzen würde: Person.find(:first, :conditions => “user = ‘#{params[:user]}’ ” + “and pw = ‘#{params[:password]}’”) Die Eingabe „'OR 1 --'“ als Passwort, ergibt die SQL-Abfrage: SELECT * FROM persons where user = ‘anyone’ and pw = ‘'OR 1 --'’ Da der letzte Teil des Ausdrucks immer „wahr“ ergibt, liefert die Abfrage immer einen Benutzer zurück, wodurch ein potentieller Angreifer Zugang erhält. Daher sollte entwe- der die Binding-Funktionalität von Active Record: Person.find(:first, :condition => [“user=? and pw=?”, user, pw]) oder die automatisch generierten dynamischen Finder verwendet werden: Person.find_by_name_and_pw(user,pw) Zur Vermeidung von XSS empfiehlt [Or07, S. 369 f.] die konsequente Benutzung der html_escape() Methode von Ruby für alle Variablen, die erst bei der Generierung der Views ausgewertet werden. Dadurch werden alle potentiellen „tainted variables“ ausgewertet, bevor der HTML-View gerendert wird. Somit kann kein Skript einge- schleust werden. Daher sollten in allen *.html.erb Dateien (View-Vorlagen) die Ru- by Variablen „“ mit „“ oder kurz „“ umschlossen werden (Vgl. Kap. 4.3). Wenn Instanzen von Modellen in Rails erzeugt oder geändert werden, bspw. bei der Registrierung eine Instanz eines neuen User-Modells, sollte immer darauf geachtet wer- den, dass über den Parameter-Hash params nicht zusätzliche Daten übertragen werden, die nicht explizit im View vorgesehen sind, wie z. B. ein admin-Attribut, welches nur in der Logik gesetzt wird. Im entsprechenden User-Model können zu schützende Attri- bute durch „attr_protected :attribut“ gekennzeichnet werden. Diese werden bei Batch-Updates wie new() oder create() nicht aktualisiert, sofern sie nicht expli- zit in der Geschäftslogik gesetzt wurden [WB06, S. 234 f.]. Einen guten Überblick aller sicherheitsrelevanten Aspekte von Rails 2.0 bietet [We08]. 21
Kapitel 5: Zusammenfassende Betrachtung 5 Zusammenfassende Betrachtung Nachdem zuerst die Grundlagen von Ruby on Rails erläutert wurden, wurde im Haupt- teil dieser Ausarbeitung gezeigt, wie einfach sich ein Prototyp einer Web-Anwendung entwickeln lässt. Im Vergleich zu anderen Web-Frameworks fällt hierbei positiv auf, dass das Paradigma „Konvention über Konfiguration“ viel Konfigurationsaufwand ers- parte. Angefangen bei der Entwicklungsumgebung, die schnell aufgesetzt war, wurde dieses insbesondere beim Prototyping sehr deutlich. Das ORM war sofort ohne Anpas- sungen nutzbar und es müssten nur die Assoziationen eingefügt werden. CRUD- Operationen konnten sofort sowohl im HTML-View als auch als RESTful Web- Services ausgeführt werden. Ein nicht zu unterschätzender Vorteil sind die Namenskon- ventionen, die dafür sorgen, dass Rails bspw. automatisch die richtigen Templates be- nutzt oder automatisch dynamische Methoden bereitstellt, wie z. B. die Finder- Methoden (find_by_attribut). Die intuitive Syntax von Ruby erleichtert das Ver- stehen des Quellcodes enorm und sieht sehr aufgeräumt aus. Grundsätzlich haben Pa- rameter sinnvolle Default-Werte, weswegen sie nicht angegeben/geändert werden müs- sen. Ruby on Rails ist ein sehr umfangreiches Framework geworden und hat speziell durch die Version 2.0 viele sinnvolle Veränderungen erfahren. Die Validierung im Mo- dell ist einfach und effektiv zugleich, ohne sich um die Darstellung zu sorgen. Nicht unwichtig ist die gute AJAX-Einbindung oder der leichte E-Mail Versand. Wenn einige sicherheitsrelevante Aspekte beachtet werden kann mit Ruby on Rails eine produktive Umsetzung erfolgen. Als Kritikpunkt ist die fehlende eingebaute Lokalisierung anzuse- hen, die eine Internationalisierung schwierig gestaltet, da diese selbst implementiert werden muss. Ab der Version 2.2 soll Rails eine einfache Lokalisierung enthalten. Au- ßerdem fehlt es im Vergleich zu J2EE-Umgebungen dem Rails Framework sicherlich an Unterstützung durch Hersteller und entsprechenden Erweiterungen. Eine Betrachtung des Aspekts Performance überstieg den Umfang dieser Ausarbeitung. Allgemein kann aber die Aussage getroffen werden, dass die Performance im Vergleich zu Java- Umgebungen schlechter ist, da Rails auf einer Interpreter-Sprache basiert. Die Perfor- mance ist aber vergleichbar oder sogar besser als bei anderen etablierten Web- Frameworks wie Django (basiert auf Python) oder Symfony (basiert auf PHP). Für eine ausführliche Betrachtung der Performance von Rails sei auf [OR07, Kap 12] verwiesen. Problematisch sind jedoch die schlechte Performance von Rails mit einer Oracle DB [Ruby08c], sowie die Inkompatibilität mit MySQL unter Windows XP (Vgl. Kap 2.4). 22
Anhang A: Quelltexte A Quelltexte Um die Umsetzung des Projektes zu dokumentieren, werden hier anhand der Reihenfol- ge der Ausarbeitung die Quelltexte zu den einzelnen Kapiteln angegeben. Es werden nur veränderte Dateien gelistet, andere nicht gelistete Dateien blieben unverändert. Da einzelne Dateien im Laufe der Ausarbeitung mehrfach verändert wurden, wird jeweils die komplett neue Datei gelistet. Falls nur kleinere Änderungen an großen Dateien vor- genommen wurden wird der unveränderte Teil der Datei mit [...] abgekürzt. Die kompletten Quelltexte und alle Dateien des Projektes sind in dem Archiv RoR_Kapitel4.zip enthalten und stellen die letzte Version des Projekts am Ende des 4. Kapitels dar. Zum Testen der Quelltexte muss eine Entwicklungsumgebung, wie in Kapitel 2.4 beschrieben, eingerichtet werden. Kapitel 3.2: class Pizza < ActiveRecord::Base # Einer Pizza werden 0..* Bestellungen zugeordnet has_and_belongs_to_many :order end Listing 22: app\models\pizza.rb class Order < ActiveRecord::Base # Einer Bestellung werden 0..* Pizzen zugeordnet has_and_belongs_to_many :pizza # Eine Bestellung wird genau einer Person zugeordnet belongs_to :user end Listing 23: app\models\order.rb class User < ActiveRecord::Base # Einem User werden 0..* Bestellungen zugeordnet has_many :order end Listing 24: app\models\user.rb class CreateOrders < ActiveRecord::Migration def self.up # Neues Schemata wird migriert create_table :orders do |t| t.text :delivery_wish t.integer :user_id # Fremdschlüssel wird ergänzt t.timestamps end # Hilfstabelle wird angelegt. Keine eigene id. create_table :orders_pizzas, :id => false do |t| t.integer :order_id # Fremdschlüssel der Bestellungen t.integer :pizza_id # Fremdschlüssel der Pizzen 23
Anhang A: Quelltexte end end def self.down # Alte Tabellen/Schemata werden gelöscht drop_table :orders drop_table :orders_pizzas # Hilfstabelle entfernen end end Listing 25: db\migrate\timestamp_create_orders.rb Kapitel 3.3: pizza1 = Pizza.new(:name => "Pizza Margherita", :price => 4.95, :ingredients => "Tomaten, Kaese") pizza2 = Pizza.new(:name => "Pizza Salami", :price => 5.45, :ingredients => "Tomaten, Kaese, Salami") pizza3 = Pizza.new(:name => "Pizza Tono", :price => 5.95, :ingredients => "Tomaten, Kaese, Thunfisch") user1 = User.new(:name => "Max Mustermann", :address => "Hansaring 55", :zip_code => 48143, :city => "Muenster") user2 = User.new(:name => "Peter Muster", :address => "Wolbecker Str. 55", :zip_code => 48143, :city => "Muenster") pizza1.save pizza2.save pizza3.save user1.save user2.save order1 = Order.new(:delivery_wish => "Bitte noch 1 Liter Cola") user1.order
Anhang A: Quelltexte class OrdersController < ApplicationController # Über den params-Hash des Select-Feldes werden die IDs # der selektierten Pizzen übergeben und der Bestellung zugeordnet def handle_pizzas_orders if params['pizza_ids'] @order.pizza.clear pizzas = params['pizza_ids'].map { |id| Pizza.find_by_id(id) } @order.pizza @order } end end [...] # POST /orders # POST /orders.xml def create @order = Order.new(params[:order]) # Weise die selektierten Pizzen der Bestellung zu handle_pizzas_orders respond_to do |format| if @order.save flash[:notice] = 'Order was successfully created.' format.html { redirect_to(@order) } format.xml { render :xml => @order, :status => :created, :location => @order } else format.html { render :action => "new" } format.xml { render :xml => @order.errors, :status => :unprocessable_entity } end end end [...] end Listing 27: app\controllers\orders_controller.rb Listing orders 25
Anhang A: Quelltexte Ordering date/time Orderer ID Order Items Delivery wish Actions 'Are you sure?', :method => :delete %> Listing 28: app\views\orders\index.html.erb New order Orderer: Pizzas: Listing 29: app\views\orders\new.html.erb 26
Sie können auch lesen