Ruby on Rails: Erste Schritte: Die erste Anwendung
In dieser Runde durch die Rails-Welt setzen wir uns folgende Ziele:
- Entwicklung einer minimalen und funktionsfähigen Web-Anwendung und
- Die Web-Anwendung soll sportliche Aktivitäten für einen bestimmten Tag beschreiben.
Hintergrund
BearbeitenRails verwendet eine Architektur, welche sich in Modell-, Präsentation und Steuerungskomponenten (MPS; engl.: MVC) gliedert. Die Entwicklung der Rails-Web-Anwendung folgt genau diesen Komponenten.
Wir beginnen mit dem Modell, welches unsere Daten beschreibt und aufnimmt.
Modell festlegen
BearbeitenMit dem Datenmodell oder kurz Modell legen wir den Aufbau der Daten fest.
Eine Rails-Web-Anwendung verwendet die eingerichtete Datenbank als Hintergrundspeicher für die dauerhafte Speicherung der Daten. Der Aufbau der Tabellen wird durch ein Datenbankschema definiert. Bei HTTP-Anfragen (HTTP request) kontaktiert das System die Datenbank und erzeugt für die benötigten Daten einzelne Ruby-Objekte. Meist werden diese Objekte mit der HTTP-Antwort (response) wieder freigegeben. Die Ruby-Objekte werden in Ruby-Klassen beschrieben.
Das gesamte Datenmodell wird also durch mindestens eine Ruby-Klasse und ein Datenbankschema festgelegt.
In unserem Fall benötigen wir für eine sportliche Aktivität ein Datum für den Tag und eine Zeichenkette für eine Beschreibung .
Datenbankschema erzeugen
BearbeitenDas Datenbankschema können wir auf zwei Arten angeben. Entweder verwenden wir
- Sql-Werkzeuge um das Datenschema mit der Data Definition Language aufzubauen oder
- Rails-Werkzeuge
Die erste Variante bietet sich immer dann an, wenn bereits Datenschemata vorliegen oder besondere Funktionen der Datenbank ausgenutzt werden sollen. Wir bleiben in der Rails-Welt und verwenden die Rails-Migrationswerkzeuge, die mit allen unterstützen Datenbanken zusammenarbeiten.
Mit
rake db:schema:dump
lassen wir uns das aktuelle Datenschema ausgeben. Das erzeugte Schema liegt unter db/schema.rb und enthält eine leere Definition.
# This file is auto-generated from the current state of the database. Instead of editing this file,
# please use the migrations feature of Active Record to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define() do
end
Wir könnten diese Datei ändern und anschließend dieses Schema automatisch in die Datenbank übertragen. Der Kommentar empfiehlt aber die Migrationsmöglichkeiten von ActiveRecord zu nutzen und mit diesen das Schema und die Datenbank schrittweise anzupassen. Einige Entwickler missachten dies für das erste Schema und verwenden die Migration erst bei Änderungen. Wir beginnen gleich mit der ersten Migration.
Mit
ruby script/generate migration add_activity
richten wir eine Migration mit dem Namen add_activity ein. Das Werkzeug erstellt zunächst ein Verzeichnis für alle Migrationen und darin die erste Migration. Der Dateiname besteht aus einer Versionsnummer und dem gewählten Namen.
create db/migrate create db/migrate/001_add_activity.rb
Die Datei db/migrate/001_add_activity.rb enthält eine Migrationsvorlage, die zunächst leer ist.
class AddActivity < ActiveRecord::Migration
def self.up
end
def self.down
end
end
Die Methode up beschreibt die Schemaänderung, welche in der Migration durchgeführt werden soll. Die Methode down beschreibt, wie die Migration zurückgenommen werden kann. Im Fall der ersten Migration beschreibt also up den Wechsel von der leeren Version 0 auf die Version 1 und down die umgekehrte Richtung.
In der up-Methode verwenden wir die Schemaanweisungen create_table und column, um die Tabelle zu erzeugen. Das Modell selbst ist schnell übertragen: Aus der sportlichen Aktivität wird die Tabelle activities, aus der Beschreibung wird die Spalte description mit dem Typ string und aus dem Tag wird die Spalte day mit dem Typ date. Bei dem Tabellennamen halten wir uns an die Rails-Konvention, dass Tabellennamen im Plural gewählt werden sollen.
In der down-Methode setzen wir drop_table ein, um die Tabelle zu zerstören.
class AddActivity < ActiveRecord::Migration
def self.up
create_table 'activities' do |t|
t.column 'description', :string
t.column 'day', :date
end
end
def self.down
drop_table 'activities'
end
end
Nachdem wir definiert haben, was in der Migration hinzukommt, bestimmen wir mit
rake db:migrate
das neue Schema. In db/schema.rb können wir uns jetzt Version 1 unseres Schemas ansehen:
# This file is auto-generated from the current state of the database. Instead of editing this file,
# please use the migrations feature of Active Record to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 1) do
create_table "activities", :force => true do |t|
t.string "description"
t.date "day"
end
end
Bei dieser ersten Migration entspricht das Schema nahezu unserer up-Definition.
Abschließend übertragen wir das Schema mit
rake db:schema:load
in die Datenbank und können mit dem Kommando sqlite3 und den Kommandos .tables und .schema unsere Datenbank db/development.sqlite3 kontrollieren
schuster $ sqlite3 db/development.sqlite3 SQLite version 3.1.3 Enter ".help" for instructions sqlite> .tables activities schema_info sqlite> .schema activities CREATE TABLE activities ("id" INTEGER PRIMARY KEY NOT NULL, "description" varchar(255), "day" date); sqlite>
Verlassen können wir die sqlite Console mit Strg + D.
Nachdem wir das Schema in die Datenbank übertragen haben, müssen wir das Schema noch in Form einer Ruby-Klasse definieren.
Ruby-Modell erzeugen
BearbeitenIm Datenbankschema haben wir im wesentlichen nur die Substanz der Daten beschrieben. Für eine Web-Anwendung im Sinne einer dreischichtigen Architektur fehlt noch die Logik, in welcher der Umgang mit den Daten geregelt wird.
Das Ruby-Modell gliedert sich wie folgt in das Rails-System ein:
- Ein Ruby-Modell wird in einer Ruby-Klasse beschrieben
- In der Ruby-Klasse wird eine Verbindung zu einem Datenbankmodell definiert
- Ein Ruby-Objekt enthält die Daten von einem Datensatz in der Datenbank
- Die Logik für die Daten wird durch das Verhalten der Ruby-Objekte angegeben
Im Mittelpunkt steht also eine Ruby-Klasse.
Mit Hilfe der Rails-Werkzeuge können wir eine solche Ruby-Klasse sehr leicht erzeugen, da wir nur die Logik angeben müssen - alles andere wird uns abgenommen. Für die zu speichernden Aktivitäten erzeugen wir mit
ruby script/generate model activity
eine Ruby-Klasse mit dem Namen Activity. Das System legt folgende Dateien an:
exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/activity.rb create test/unit/activity_test.rb create test/fixtures/activities.yml
Unter test/unit/activity_test.rb wurden Test-Klassen vorbereitet und unter test/fixtures/activities.yml können Testdaten hinterlegt werden. In app/models/activity.rb wurde die Ruby-Klasse Activity für das Datenmodell erzeugt:
class Activity < ActiveRecord::Base
end
Im Gegensatz zum activity-Ruby-Schema und zum activities-Datenbankschema ist die Activity-Klasse leer. Die eigentliche Arbeit wird in der generischen Oberklasse ActiveRecord erledigt. Solange wir keine Logik benötigen, wird die Klasse erst einmal leer bleiben.
Ohne zusätzliche Angaben greift gemäß Konvention die Klasse auf die gleichnamige Tabelle (Tabellenname im Plural) zu. Für die Spalten der Tabelle werden automatisch Lese- und Schreibmethoden (http://de.wikipedia.org/wiki/Zugriffsfunktion getter und setter) bereitgestellt. Mit der Rails-console und sqlite3 können wir dies sofort überprüfen.
Wir legen mit new einen Datensatz an und speichern diesen mit save! in der Datenbank:
schuster $ ruby script/console Loading development environment. >> a = Activity.new :description => 'Leichtes warmlaufen', :day => Date.new(2006, 4, 17) => #<Activity:0x24520a8 @attributes={"day"=>#<Date: 4907685/2,0,2299161>, "description"=>"Leichtes warmlaufen"}, @new_record=true> >> a.save! => true >> quit
Anschließend sehen wir uns mit sqlite3 und SELECT die Datensätze in der Datenbank an:
schuster $ sqlite3 db/development.sqlite3 SQLite version 3.1.3 Enter ".help" for instructions sqlite> SELECT * FROM activities; 1|Leichtes warmlaufen|2006-04-17
Fantastisch: Ohne große Mühen haben wir unser Datenmodell implementiert, die ersten Daten eingegeben und ohne Krämpfe die Daten von Rails in die Datenbank gespeichert. Gehen wir zur Web-Darstellung über.
Steuerung definieren
BearbeitenDie Steuerung ist in einer MPS-Architektur (MVC) das Bindeglied zwischen dem Benutzer, den Daten (Modell) und deren Ansichten (Präsentation). Benutzereingaben und Modelländerungen werden ausgewertet und führen wiederum zu Modelländerungen und Ausgaben.
Im Rails-System wird im allgemeinen ein Modell mit einer Steuerung versehen, für die ihrerseits mehrere Ansichten definiert werden.
Mit
ruby script/generate controller activity
erzeugen wir eine Steuerung für das activity-Modell. Zusätzlich zur Steuerung werden weitere Komponenten erzeugt, die uns jetzt nicht interessieren.
exists app/controllers/ exists app/helpers/ create app/views/activity exists test/functional/ create app/controllers/activity_controller.rb create test/functional/activity_controller_test.rb create app/helpers/activity_helper.rb
In app/controllers/activity_controller.rb wurde eine Vorlage für die gewünschte Steuerung erstellt
class ActivityController < ApplicationController
end
Die Klasse ist eine Unterklasse von ApplicationController und ist leer. Der Klassenname enthält activity, ist aber noch nicht mit dem activity-Modell verknüpft.
Bevor wir die Steuerung für individuelle Sichten (Anzeigen, Erstellen, Ändern, etc.) erweitern, nehmen wir eine Abkürzung. Wir fügen in app/controllers/activity_controller.rb die scaffold-Anweisung ein.
class ActivityController < ApplicationController
scaffold :activity
end
Mit dieser einen Zeile haben wir die Steuerung mit den Standardsichten und den Standardaktionen für das activity-Modell ausgestattet. Dazu gehören:
- eine Übersicht, entspricht der Listenansicht (index)
- eine Listenansicht aller Datensätze (list)
- eine Erstellungsansicht (new, create)
- eine Änderungsansicht (edit, update)
Für keine Ansicht wird Quellcode generiert; stattdessen erzeugt die scaffold-Anweisung generisch zur Laufzeit Sichten und Aktionen.
Schwer zu glauben, dass wir unser gestecktes Ziel schon erreicht haben. Sehen wir uns das Ganze also an.
Server starten
BearbeitenNachdem wir das Sql-Schema, die Ruby-Klassen und die Rails-Steuerung erzeugt und die generischen Sichten aktiviert haben, starten wir den Rails-Server
ruby script/server
und sehen uns die Web-Seite unter http://127.0.0.1:3000/activity an.