Ruby on Rails: Erste Schritte: Die erste Anwendung

Aus Wikibooks

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[Bearbeiten]

Rails 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[Bearbeiten]

Mit 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[Bearbeiten]

Das Datenbankschema können wir auf zwei Arten angeben. Entweder verwenden wir

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[Bearbeiten]

Im 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[Bearbeiten]

Die 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[Bearbeiten]

Nachdem 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.