Händler-Anwendung (Ruby-on-Rails-Beispiel): Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Kowa (Diskussion | Beiträge)
Die Seite wurde neu angelegt: = Zweck = Anhand einer einfachen Web-Anwendung sollten die Prinzipien von Ruby on Rails erläutert werden. = Voraussetzungen = Folgende Softwarepakete wurden ins...
 
Kowa (Diskussion | Beiträge)
Keine Bearbeitungszusammenfassung
 
(160 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
= Zweck =
'''veraltet'''
{{Qualität
|correctness        = 2
|extent              = 3
|numberOfReferences  = 4
|qualityOfReferences = 4
|conformance        = 4
}}
== Zweck ==


Anhand einer einfachen Web-Anwendung sollten die Prinzipien von [[Ruby on Rails]] erläutert werden.
Anhand einer einfachen Web-Anwendung sollten die Prinzipien von [[Ruby on Rails]] erläutert werden.


= Voraussetzungen =
Diesem Beispiel liegt die Händler-Datenbank zugrunde, die [[Benutzer:Kowa|Wolfgang Kowarschick]] in der Vorlesung Datenmanagement II (Multimedia-Datenbanksysteme)<ref>{{Quelle|Kowarschick (MMDB-Skript)}}</ref> als Beispiel einsetzt:
 
[[Datei:Haendler.png]]
 
== Voraussetzungen ==


Folgende Softwarepakete wurden installiert:
Folgende Softwarepakete wurden installiert:
* [[Apache|Apache 2.2.x]]
* [[Apache|Apache 2.2.x]]
* [[PostgreSQL||PostgreSQL 8.x]]
* [[PostgreSQL|PostgreSQL 8.x]] (am Besten zusammen mit einem Frontend wie z.B [[phpPgAdmin]])
* [[Ruby|Ruby 1.8.7]]
* [[Ruby|Ruby 1.8.7]]
* [[Ruby on Rails]]
* [[Ruby on Rails|Ruby on Rails 2.2]]


Ruby on Rails wurd ezusammen mit einigen Erweiterungs-Modulen mit Hilfe des Ruby-Befehls <code>gem</code> installiert:
Ruby on Rails wurde zusammen mit einigen Erweiterungs-Modulen mit Hilfe des Ruby-Paketmanagers <code>gem</code> installiert:


<source lang="bash">
<source lang="bash">
  RUBY_DIR=...; export RUBY_DIR
RUBY_DIR=...; export RUBY_DIR
  POSTGRESQL_DIR=...; export  POSTGRESQL_DIR
POSTGRESQL_DIR=...; export  POSTGRESQL_DIR


  gem install rails --include-dependencies
gem install rails
  gem install postgres -r -- --with-pgsql-dir=${POSTGRESQL_DIR}
gem install postgres -r -- --with-pgsql-dir=${POSTGRESQL_DIR}
  gem install passenger
gem install passenger
  ${RUBY_DIR}/bin/passenger-install-apache2-module
${RUBY_DIR}/bin/passenger-install-apache2-module
</source>
 
Weitere sinnvolle Pakete:
<source lang="bash">
gem install rdoc
gem install mongrel
gem install ruby-debug-ide
gem install ruby-debug-base
gem install ZenTest
gem install rspec
gem install rspec-rails
</source>
</source>


Auf dem Test-Server steht in der Datei <code>/etc/hosts</code> ein
Auf dem Test-Server steht in der Datei <code>/etc/hosts</code> ein
Eintrag für den geüwnschten virtuellen Server, auf dem die Anwendung laufen soll (die IP-Adresse
Eintrag für den gewünschten virtuellen Server, auf dem die Anwendung laufen soll (die IP-Adresse
und der Rechnernamen können natürlich angepasst werden):
und der Rechnernamen können natürlich angepasst werden):


<source lang="text">
<source lang="text">
  192.168.0.161  haendler.kowa haendler
192.168.0.161  haendler.kowa haendler
</source>
</source>
   
   
Diese IP-Adresse ist einem echten oder virtuellen Rechner zugeordnet.
Diese IP-Adresse ist einem echten oder virtuellen Rechner zugeordnet.
Unter [[SuSE|SuSE 11]] kann man einem Rechner ganz einfach mehrere IP-Adressen zuordnen.
Unter [[SuSE|SuSE 11]] kann man einem Rechner ganz einfach mehrere IP-Adressen zuordnen.
Man trägt die neue IP-Adresse einfach in die Datei <code>/etc/sysconfig/network/ifcfg-eth0</code> ein:
Man trägt die neue IP-Adresse in die Datei <code>/etc/sysconfig/network/ifcfg-eth0</code> ein:


<source lang="text">
<source lang="text">
  IPADDR_haendler='192.168.0.161'
IPADDR_haendler='192.168.0.161'
  NETMASK_haendler='255.255.255.0'
NETMASK_haendler='255.255.255.0'
</source>
</source>
   
   
Danach wird der Netzwerzugriff neu gestartet:
Danach muss der Netzwerkzugriff neu gestartet werden:


<source lang="bash">
<source lang="bash">
  /etc/init.d/network restart
/etc/init.d/network restart
</source>
</source>


= Anlegen einer Rails-Anwendung =
== Anlegen der Händlerdatenbanken ==
 
Für jede Rails-Anwendung müssen stets drei Datenbanken existieren, eine für die Entwicklung, eine
für die automatischen Tests und eine für den produktiven Betrieb. Außerdem sollte für jede Rails-Anwendung
ein spezieller Benutzer definiert werden, dem diese drei Datenbanken gehören.
Der Benutzer und die Datenbanken können über eine Web-Schnittstelle wie <code>[[phpPgAdmin]]</code>
oder auch mit Hilfe von einfachen PostgreSQL-Befehlen angelegt werden:


<source lang="bash">
<source lang="bash">
    
createuser haendler -d -e -E -P -S -R
 
createdb -Uhaendler -Ohaendler haendler_development
createdb -Uhaendler -Ohaendler haendler_test
createdb -Uhaendler -Ohaendler haendler_production
</source>
 
== Anlegen der Rails-Anwendung <code>haendler</code>==
 
Zunächst werden im Rails-Webverzeichnis die Ordner und Dateien einer neuen Rails-Anwendung namens <code>haendler</code> erzeugt:
 
<source lang="bash">
cd /web/rails
rails --database postgresql haendler
</source>
 
In der Konfigurations-Datei <code>database.yml</code>,
die den Zugriff auf das DB-System regelt, wird das Passwort für den Benutzer
<code>haendler</code> an drei Stellen eingetragen:
 
<source lang="bash">
vi /web/rails/haendler/config/database.yml
</source>
<source lang="text">
development:
   adapter: postgresql
  encoding: unicode
  database: haendler_development
  pool: 5
  username: haendler
  password: geHEIM!?
 
test:
  adapter: postgresql
  encoding: unicode
  database: haendler_test
  pool: 5
  username: haendler
  password: geHEIM!?
 
production:
  adapter: postgresql
  encoding: unicode
  database: haendler_production
  pool: 5
  username: haendler
  password: geHEIM!?
</source>
 
Mit Hilfe eines <code>rake</code>-Kommandos kann überprüft werden, ob die
Verbindung zur Datenbank hergestellt werden kann:
<source lang="bash">
cd /web/rails/haendler
rake db:migrate
</source>
Wenn das <code>rake</code>-Kommando keinen Fehler ausgibt, ist alles in Ordnung.
 
== Zugang zur Web-Anwendung <code>haendler</code> mit Hilfe von Apache ==
 
Unter der Annahme, dass Rails-Anwendungen im Verzeichnis <code>/web/rails</code>
angelegt werden, kann der virtuelle Server in der Apache-Konfigurationsdatei <code>httpd.conf</code>
eingetragen werden:
 
<source lang="apache">
<VirtualHost 192.168.0.161:80>
  ServerName haendler.kowa
  DocumentRoot /web/rails/haendler/public
  <Directory /web/rails/haendler/public>
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>
 
  CustomLog    logs/haendler-access combined
  ErrorLog    logs/haendler-error
 
</VirtualHost>
</source>
 
Nach einem Neustart von Apache mit <code>/etc/rc.d/apache restart</code> oder, schneller,
<code>kill -1 <Prozessnummer des Apache-Root-Prozesses></code> sollte man die Applikation
im Browser ansehen können:
 
<source lang="text">
http://haendler.kowa/
</source>
 
===Anmerkung===
Wann immer an der Rails-Anwendung eine Änderung vorgenommen wurde, sollte man im <code>tmp</code>-Verzeichnis
der Anwendung eine Datei namens <code>restart.txt</code> (mit beliebigem Inhalt) angelegt werden. Dadurch wird
Apache informiert, dass der die Anwendung neu einlesen soll. Und somit werden alle Änderungen direkt im Browser sichtbar.
 
<source lang="bash">
touch /web/rails/haendler/tmp/restart.txt
</source>
 
== Konvention: Klassennamen stehen im Singular und Datenbanktabellen-Namen im Plural==
 
Für Ruby on Rails wurde die Konvention eingeführt, dass Klassennamen stets in Singular stehen und mit einem Großbuchstaben beginnen: <code>Haendler</code>,
<code>Liefert</code>, <code>Ware</code>. Die zugehörigen Tabellennamen sollen dagegen im Plural stehen und kleingeschrieben werden.
Die zweite Regel führt allerdings bei deutschen Tabellennamen sowie bei englischnamigen Beziehungstabellen zu unschönen Ergebnissen, wenn die Tabellen ''automatisch'' gemäß Konvention z.B. mittels
<code>ruby script/generate</code> erzeugt werden:
<code>haendlers</code>, <code>lieferts</code>, <code>wares</code> bzw. <code>traders</code>, <code>supplies</code>, <code>items</code> (hier
wäre <code>supply</code> besser).
 
Wenn man die Ruby-Konvention beachten will, sollte man die falsche automatische Plural-Bildung verhindern, indem man Ausnahmen in die Datei <code>inflections.rb</code> einfügt:
 
<source lang="bash">
vi /web/rails/haendler/config/initializers/inflections.rb
</source>
<source lang="text">
ActiveSupport::Inflector.inflections do |inflect|
  inflect.uncountable 'haendler'
  inflect.irregular  'liefert', 'liefern'
  inflect.irregular  'ware',    'waren'
end
</source>
 
==== Anmerkung 1 ====
 
Klassennamen für die Singular und Plural übereinstimmen,
bereiten beim automatischen Erzeugen von Zugriffsmethoden („Scaffolding“) Probleme, da in den URLs nicht zwischen
der Menge aller zugehörigen Objekten und einem einzelen Objekt unterschieden werden kann.
 
Diese Probleme kommen hier nicht zum Tragen, da Scaffolding sowieso nicht verwendet wird.
 
==== Anmerkung 2 ====
 
In allen meinen Programmen und Vorlesungsbeispielen halte ich mich normalerweise an die Konvention, dass sowohl Klassen-, als auch Tabellennamen im Singular geschrieben werden sollten. Dies könnte man in Ruby on Rails erreichen, indem man eine Initialisierungs-Datei <code>pluralize_false.rb</code>
mit folgenden Inhalt anlegt:
 
<source lang="bash">
# vi /web/rails/haendler/config/initializers/pluralize_false.rb
</source>
<source lang="text">
ActiveRecord::Base.pluralize_table_names = false
</source>
 
Das es nun einmal Ruby-on-Rails-Konvention ist, Tabellennamen im Plural zu schreiben, greife ich in diesem Beispiel nicht auf diese Möglichkeit zurück.
 
== Anlegen des Modells ==
=== Anlegen der Modell-Dateien ===
 
Mit Hilfe des Ruby-Skriptes <code>ruby script/generate model</code> kann nun die Dateien des Modells, d.h. die Dateien für die Klassen- und Tabellen-Definitionen des Modells angelegt werden.
 
<source lang="bash">
cd /web/rails/haendler/
ruby script/generate model haendler
ruby script/generate model ware
ruby script/generate model liefert
</source>
 
Ergebnis:
<source lang="bash">
exists  app/models/
exists  test/unit/
exists  test/fixtures/
create  app/models/haendler.rb
create  test/unit/haendler_test.rb
create  test/fixtures/haendlers.yml
create  db/migrate
create  db/migrate/20081208135556_create_haendlers.rb
 
...
</source>
 
=== Primär- und Fremdschlüssel ===
 
Laut Ruby-on-Rails-Philosophie sollte jede Tabelle ein Primärschlüssel-Attribut namens <code>id</code> enthalten.
Dieses wird, wenn man in den zugehörigen Migrate-Dateien nichts anderes angibt, mittels
<source lang="bash">
rake db:migrate
</source>
automatisch erzeugt.
 
Nun benötigen Beziehungstabellen kein künstliches Schlüssel-Attribut. Eine übliche Methode ist es daher, in Rails-Anwendungen
bei Beziehungstabellen ganz auf Primärschlüssel zu verzichten und anstatt dessen einen Index für die Beziehungsattribute zu definieren
(siehe z.B. den Abschnitt "Migration" in der Rails-Kurz-Referenz von InVisible<ref>http://blog.invisible.ch/files/rails-reference-1.1.html#migration</ref>). Sollte eine Beziehungstabelle weitere Attribute haben,
so müsste für die Beziehungsattribute zusätzlich noch eine gemeinsame Unique-Bedingung angegeben werden. Dies wird für mehr als ein Attribut von Rails
leider auch nicht direkt unterstüzt (vgl. z.B. die Dokumentation zur Klasse
<code>ActiveRecord::Migration</code><ref>http://api.rubyonrails.org/classes/ActiveRecord/Migration.html</ref>, Abschnitt „More examples“).
 
In einem sehr schönen Blog-Beitrag<ref>http://weblogs.sqlteam.com/jeffs/archive/2007/08/23/composite_primary_keys.aspx</ref> beschreibt
Jeff Smith, warum im Falle von Beziehungstabellen „Composite Primary Keys“, wie Multi-Attribut-Primärschlüssel im Englischen genannt werden,
so wichtig sind. Er widerlegt dabei auch in einer Antwort auf eine Leserzuschrift die Argumente von Lee Richardson<ref>http://rapidapplicationdevelopment.blogspot.com/2007/08/in-case-youre-new-to-series-ive.html</ref>,
der gegen „Composite Primary Keys“ argumentiert.
 
Ruby on Rails unterstützt nicht nur die Angabe von Multi-Attribut-Primärschlüsseln nicht, auch die Angabe von Fremdschlüsseln wird nicht direkt unterstützt.
 
Glücklicherweise gibt es zwei Erweiterungspakete, die diese Probleme zumindest teilweise beheben.
 
Die Arbeit mit Multi-Primärschlüsseln ermöglicht das Rails-Paket <code>composite_primary_keys</code>.
Und mit Hilfe des Pakets <code>foreign_key_migrations</code> können auch Foreign Keys innerhalb von Migration-Dateien definiert werden:
 
<source lang="bash">
gem install composite_primary_keys
gem install foreign_key_migrations
</source>
 
Um die beiden Pakete verwenden zu können, müssen sie in der Händler-Anwendung noch aktiviert werden:
 
<source lang="bash">
vi /web/rails/haendler/config/initializers/db_extensions.rb
</source>
<source lang="ruby">
require 'composite_primary_keys'
require 'composite_primary_keys/migration'
require 'foreign_key_migrations'
</source>
 
Beide Pakete sorgen jedoch leider nicht dafür, dass Multi-Primärschlüssel und Foreign Keys auch in der automatisch generierten Datei <code>schema.db</code> aufgeführt werden (siehe [[Händler-Anwendung (Ruby-on-Rails-Beispiel)#Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen|Abschnitt „Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen“]]).
 
==== ActiveRecord-PostgreSQL-Patch ====
 
Wenn man mit Fremdschlüssel arbeitet, stolpert man ziemlich schnell über einen Rails-PostgreSQL-Bug<ref>http://kopongo.com/2008/7/25/postgres-ri_constrainttrigger-error</ref>. Diesen kann man mit folgendem Patch beheben:
 
<source lang="bash">
vi /web/rails/haendler/config/initializers/active_record_postgresql.rb
</source>
<source lang="ruby">
module ActiveRecord
  module ConnectionAdapters
    class PostgreSQLAdapter < AbstractAdapter
      def disable_referential_integrity(&block)
        transaction {
          begin
            execute "SET CONSTRAINTS ALL DEFERRED"
            yield
          ensure
            execute "SET CONSTRAINTS ALL IMMEDIATE"
          end
        }
      end
    end
  end
end
</source>
 
=== Bearbeiten der Migrate-Dateien ===
 
Nun werden die Attribute, Schlüssel, Fremdschlüssel und Indexe der einzelnen Klassen des Modells
in den zugehörigen Migrate-Dateien eingetragen.
 
<source lang="bash">
vi /web/rails/haendler/db/migrate/*_create_haendler.rb
</source>
 
<source lang="ruby">
class CreateHaendler < ActiveRecord::Migration
  def self.up
    create_table :haendler do |t|
      t.string :name,  :null => false
      t.string :adresse
 
      t.timestamps
    end
  end
 
  def self.down
    remove_index :liefern, [:ware_id]
    drop_table :haendler
  end
end
</source>
 
<source lang="bash">
vi /web/rails/haendler/db/migrate/*_create_waren.rb
</source>
 
<source lang="ruby">
class CreateWaren < ActiveRecord::Migration
  def self.up
    create_table :waren do |t|
      t.string :typ,        :null => false
      t.string :bezeichnung, :null => false
 
      t.timestamps
    end
 
    execute "ALTER TABLE waren ADD CONSTRAINT unique_typ_bezeichnung UNIQUE (typ, bezeichnung)"
  end
 
  def self.down
    drop_table :waren
  end
end
</source>
 
 
<source lang="bash">
vi /web/rails/haendler/db/migrate/*_create_liefern.rb
</source>
 
<source lang="ruby">
class CreateLiefern < ActiveRecord::Migration
  def self.up
    create_table :liefern, :primary_key => [:haendler_id, :ware_id, :preis] do |t|
      t.integer :haendler_id, :null => false, :references => :haendler
      t.integer :ware_id,    :null => false, :references => :waren
      t.decimal :preis,      :null => false, :precision => 6, :scale => 2
      t.integer :lieferzeit
 
      t.timestamps
    end
 
    add_index :liefern, [:ware_id]
  end
 
  def self.down
    drop_table :liefern
  end
end
</source>
 
Anschließend werden die Tabellen mit dem Ruby-Make-Befehl <code>rake</code> erzeugt:
 
<source lang="bash">
cd /web/rails/haendler
rake db:migrate
rake db:migrate RAILS_ENV="production"
</source>
 
====Anmerkung====
 
Mit
<source lang="bash">
rake db:migrate                        VERSION="0"
rake db:migrate RAILS_ENV="production" VERSION="0"
</source>
können alle Tabellen wieder gelöscht werden.
 
== Bearbeitung der Klassen des Modells ==
Die Beziehungen und sonstigen Eigenschaften der Modell-Klassen sollten
in den durch <code>ruby script/generate model</code> angelegten Modell-Dateien
angegeben werden. Dadurch ist es möglich, auf die Daten in der Datenbank zuzugreifen,
ohne SQL-Code schreiben zu müssen.
 
<source lang="bash">
vi /web/rails/haendler/app/models/haendler.rb
</source>
<source lang="ruby">
class Haendler < ActiveRecord::Base
  has_many :waren, :through => :liefern
  has_many :liefern
 
  validates_presence_of :name
end
</source>
 
<source lang="bash">
vi /web/rails/haendler/app/models/ware.rb
</source>
<source lang="ruby">
class Ware < ActiveRecord::Base
  has_many :haendler, :through => :liefern
  has_many :liefern
 
  validates_presence_of :typ, :bezeichnung
end
</source>
 
<source lang="bash">
vi /web/rails/haendler/app/models/liefert.rb
</source>
<source lang="ruby">
class Liefert < ActiveRecord::Base
  belongs_to :haendler, :foreign_key => :haendler_id
  belongs_to :waren,    :foreign_key => :ware_id
 
  set_primary_keys [:haendler_id, :ware_id, :preis]
  validates_presence_of :haendler_id, :ware_id, :preis
end
</source>
 
Anschließend sollte mit einem Browser Ihrer Wahl überprüft werden, ob irgendwelche Fehlermeldungen
ausgegeben werden:
 
<source lang="bash">
touch /web/rails/haendler/tmp/restart.txt
lynx http://haendler.kowa/test
</source>
 
Wenn der Server mit der Fehlermeldung 404 anzeigt, dass es diese Seite nicht gibt, ist alles OK. Views wurden ja noch nicht definiert.
Wenn der Server allerdings mit der Fehlermeldung 500 anzeigt, dass ein interner Server-Fehler vorliegt, wurden die Modell-Dateien nicht korrekt
definiert. Der Server gibt dann auch eine genauere Fehlermeldung aus.
 
=== Anlegen der Testdaten ===
 
In Rails sollten für jede Anwendung Tests generiert werden, um die Integrität der Anwendung jederzeit
auotmatisch testen zu können. Insbesondere müssen Test-Daten definiert werden, mit denen bei Start eines Test
die Test-Datenbank initialisiert wird.
 
<source lang="bash">
vi /web/rails/haendler/test/fixtures/haendler.yml
</source>
<source lang="text">
maier_koenigsbrunn:
  id:      1
  name:    Maier
  adresse: Königsbrunn
 
mueller_koenigsbrunn:
  id:      2
  name:    Müller
  adresse: Königsbrunn
 
maier_augsburg:
  id:      3
  name:    Maier
  adresse: Augsburg
 
huber:
  id:      4
  name:    Huber
</source>
 
<source lang="bash">
vi /web/rails/haendler/test/fixtures/waren.yml
</source>
<source lang="text">
cup1:
  id:          1
  typ:        CPU
  bezeichnung: Pentium IV 3,8
 
cpu2:
  id:      2
  typ:        CPU
  bezeichnung: Celeron 2,6'
 
cpu3:
  id:      3
  typ:        CPU
  bezeichnung: Athlon XP 3000+
 
eieruhr:
  id:      4
  typ:        Sonstiges
  bezeichnung: Eieruhr
</source>
 
<source lang="bash">
vi /web/rails/haendler/test/fixtures/liefern.yml
</source>
<source lang="text">
mak_c1:
  haendler_id:      1
  ware_id:          1
  preis:      200.00
  lieferzeit:      1
 
mak_c2:
  haendler_id:      1
  ware_id:          2
  preis:      100.00
  lieferzeit:      1
 
mak_c3:
  haendler_id:      1
  ware_id:          3
  preis:      150.00
 
mak_3:
  haendler_id:      1
  ware_id:          4
  preis:        10.00
  lieferzeit:      1
 
muk_cp1:
  haendler_id:      2
  ware_id:          1
  preis:      160.00
  lieferzeit:      1
 
muk_cp2:
  haendler_id:      2
  ware_id:          2
  preis:      180.00
 
muk_cp3:
  haendler_id:      2
  ware_id:          3
  preis:      150.00
  lieferzeit:      4
 
maa_cp1:
  haendler_id:      3
  ware_id:          1
  preis:      160.00
  lieferzeit:      4
 
maa_cp2:
  haendler_id:      3
  ware_id:          2
  preis:      190.00
  lieferzeit:      1
 
hu_cp1:
  haendler_id:      4
  ware_id:          1
  preis:      150.00
  lieferzeit:      4
 
hu_cp3:
  haendler_id:      4
  ware_id:          3
  preis:      180.00
  lieferzeit:      5
 
hu_cp3_2:
  haendler_id:      4
  ware_id:          3
  preis:      199.00
  lieferzeit:      1
</source>
 
Ob alle Daten korrekt eingetragen wurden, kann man überprüfen, indem man
eine Rails-Testlauf startet. Dies geschieht einfach durch Aufruf des Befehls <code>rake</code>.
Daran, dass das Testen die Default-Aktion von <code>rake</code> ist,
erkennt man die Bedeutung, die die Rails-Gemeinde dem Testen beimisst. 
 
<source lang="bash">
cd /web/rails/haendler
rake
</source>
 
=== Testen des Modells ===
==== Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen ====
 
Wenn man die Test-Tabellen mit <code>db:migrate</code> erzeugt,
werden alle Primär- und Fremdschlüssel sowie die Integritätsbedingung <code>unique_typ_bezeichnung</code>
wie gewünscht in der Test-Datenbank angelegt:
 
<source lang="bash">
rake db:migrate RAILS_ENV="test" VERSION=0
rake db:migrate RAILS_ENV="test"
</source>
 
Erzeugt man die Test-Datenbank hingegen mit
<source lang="bash">
rake
</source>
oder
<source lang="bash">
rake db:test:prepare
</source>
fehlen die mit Hilfe der gem-Pakete <code>composite_primary_keys</code> und <code>foreign_key_migrations</code>
angelegten Primär- und Fremdschlüssel sowie der Index <code>unique_typ_bezeichnung</code>.
 
Dies ist ziemlich nachteilig, da sich die Datenbank-Struktur der Test-Datenbank nicht von
der Datenbankstruktur der beiden anderen Datenbanken unterscheiden sollte.
 
Der Grund für dieses Verhalten ist, dass anstelle der Migrations-Dateien die Datei
<code>/web/rails/haendler/db/schema.db</code> zum Erzeugen der Test-Datenbank verwendet wird.
Diese Datei wird bei jedem Aufruf von <code>rake db:migrate</code> automatisch erzeugt.
Leider werden dabei die Primär- und Fremdschlüssel nicht korrekt übernommen.
 
Dieses Problem kann derzeit am einfachsten behoben werden, indem man eine Datei <code>schema_sound.db</code>
von Hand erzeugt, die die korrekten Schema-Definitionen enthält. Jedesmal, wenn die
Datei <code>schema.db</code> neu generiert wird, sollte man diese Datei mit
der Datei <code>schema_sound.db</code> überschreiben. Eventuell muss zuvor die Datei
<code>schema_sound.db</code> gemäß den an den Migrations-Dateien vorgenommenen Änderungen
ebenfalls modifiziert werden.
 
<source lang="bash">
vi /web/rails/haendler/db/schema_sound.rb
</source>
<source lang="ruby">
# Die Versionsnummer sollte aus der aktuellen schema.db übernommen werden.
ActiveRecord::Schema.define(:version => 20081216130913) do
 
  create_table "haendler", :force => true do |t|
    t.string "name",    :null => false
    t.string "adresse"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
 
  create_table "waren", :force => true do |t|
    t.string "typ",        :null => false
    t.string "bezeichnung", :null => false
    t.datetime "created_at"
    t.datetime "updated_at"
  end
 
  execute "ALTER TABLE waren ADD CONSTRAINT unique_typ_bezeichnung UNIQUE (typ, bezeichnung)"
 
  create_table "liefern", :primary_key => [:haendler_id, :ware_id, :preis], :force => true  do |t|
    t.integer "haendler_id",                              :null => false, :references => :haendler
    t.integer "ware_id",                                  :null => false, :references => :waren
    t.decimal "preis",      :precision => 6, :scale => 2, :null => false
    t.integer "lieferzeit"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
 
  add_index "liefern", ["ware_id"], :name => "index_liefern_on_ware_id"
 
end
</source>
 
==== Anlegen der Tests ====
 
Beim Testen der Anwendung werden die Test-Methoden aller Dateien, die in einem der Ordner
* <code>test/unit</code> (Modell-Tests)
* <code>test/functional</code> (Controller- und View-Tests)
* <code>test/integration</code> (Test zur Interaktion zwischen Benutzer und Anwendung)
* <code>test/performance</code> (Performanz-Tests)
gefunden werden und deren Namen mit <code>_test</code> enden, der Reihe nach durchgeführt.
Jede dieser Test-Methoden soll
mit Hilfe der Methoden <code>assert</code>, <code>assert_generates</code> etc.
sicherstellen, dass bestimmte [[Integritätsbedingung]]en
von der Anwendung nicht verletzt werden.
 
Zum Beispiel kann man überprüfen, ob der Primär- und die Fremdschlüssel der Tabelle <code>liefern</code>
korrekt beachtet werden:
 
<source lang="bash">
vi /wkcms/web/rails/haendler/test/unit/liefert_test.rb
</source>
<source lang="ruby">
require 'test_helper'
 
class LiefertTest < ActiveSupport::TestCase
  test "foreign key haendler" do
    liefert1 = Liefert.new(:haendler_id => 6,
                          :ware_id    => 1,
                          :preis      => 199.00
                          )
    assert liefert1.valid?
 
    result1 = false
    begin
      liefert1.save
    rescue
      result1 = true # Händler 6 existiert nicht.
    end
    assert result1
  end
 
  test "foreign key waren" do
    liefert2 = Liefert.new(:haendler_id => 1,
                          :ware_id    => 7,
                          :preis      => 399.00
                          )
    assert liefert2.valid?
 
    result2 = false
    begin
      liefert2.save
    rescue
      result2 = true # Ware 7 existiert nicht.
    end
    assert result2
  end
 
  test "primary key" do
    liefert = Liefert.new(:haendler_id => 1,
                          :ware_id    => 1,
                          :preis      => 200.00,
                          :lieferzeit  => 5
                        )
    assert liefert.valid?
 
    result = false
    begin
      liefert.save
    rescue
      result = true # Es gibt schon einen Eintrag in der Datenbank,
                    # dass Händler 1 die Ware 1 zum Preis von 200.00
                    # innerhalb eines Tages liefert.
    end
    assert result
  end
 
end
</source>
 
==== Durchführen der Tests ====
 
Wenn man nun die Tests mit der originalen <code>schema.db</code> durchführt, erhält man drei Assert-Fehler:
 
<source lang="bash">
cd /web/rails/haendler
rake
 
> 5 tests, 8 assertions, 3 failures, 0 errors
</source>
 
Der Grund ist, dass alle drei Objekte, die in der Datei <code>liefert_test.rb</code>
definiert werden, problemlos in die Test-Datenbank eingefügt werden können.
Das heißt, der Primär- und die Fremdschlüssel werden nicht beachtet.
 
Um dies zu erreichen, muss man die falsche Datei <code>schema.db</code> durch die richtige
ersetzen.
 
<source lang="bash">
cp /web/rails/haendler/db/schema_sound.rb /web/rails/haendler/db/schema.rb
</source>
 
Allerdings funktioniert der Test jetzt immer noch nicht, sondern endet nun sogar mit fünf
Laufzeit-Fehlern. Der Grund ist, dass die Datenbank-Tabellen in alphabetischer Reihenfolge
gefüllt werden. Aufgrund der Fremdschlüssel muss aber die Tabelle <code>liefern</code>
als letzte gefüllt werden. Die erreicht man, in dem man
die Reihenfolge der drei Tabellen explizit in der Datei <code>test_helper.rb</code>
angibt:
 
<source lang="bash">
vi /web/rails/haendler/test/test_helper.rb
</source>
<source lang="ruby">
#fixtures :all  ## :all bedeutet "Initialisierung in alphabetischer Reihenfolge"
  fixtures :haendler, :waren, :liefern
</source>
 
Nun sollten alle Tests fehlerfrei funktionieren:
 
<source lang="bash">
cd /web/rails/haendler
rake
 
> 5 tests, 8 assertions, 0 failures, 0 errors
</source>
 
Das <code>gem</code>-Paket <code>ZenTest</code> ermöglicht automatische Test.
Sobald man
<source lang="bash">
cd /web/rails/haendler
autotest
</source>
eingegeben hat, werden nach jeder Änderung der Anwendun die Tests automatisch gestartet.
 
== Anlegen der Controller- und der View-Dateien ==
=== Scaffolding ===
 
'''In diesem Beispiel werde ich auf Scaffolding verzichten.'''
 
Mit Hilfe des Scaffold-Skriptes könnten ein paar Controller- und View-Dateien angelegt werden,
um Waren erfassen, bearbeiten und löschen zu können.
 
<source lang="bash">
cd /web/rails/haendler
ruby script/generate scaffold ware typ:string bezeichnung:string
 
touch /web/rails/haendler/tmp/restart.txt
lynx http://haendler.kowa/waren/
</source>
 
Leider funktioniert diese Methode nicht problemlos für die Klasse <code>Haendler</code>,
da hier „Singular gleich Plural“ gilt.
 
=== Erzeugung von einigen Controller- und View-Dateien ===
 
Bislang können die Inhalte der Datenbank weder im Web präsentiert werden,
noch ist es möglich, sie über ein
Web-Interface zu modifizieren. Um dies zu erreichen brauchen man
View-Templates (für die Präsentation) und Controller-Klassen (für die
Verarbeitung der Benutzeraktionen).
 
Zunächst sollten die wichtigsten Dateien automatisch erzeugt werden:
<source lang="bash">
cd /web/rails/haendler
 
ruby script/generate rspec
ruby script/generate controller Haendler index
ruby script/generate controller Ware    index
</source>
</source>


=Quellen=
==Quellen==


*[[Kowarschick, W.: Content-Management]]
<references />
<ol>
<li value="8">{{Quelle|Kowarschick, W.: Content-Management}}</li>
<li>[http://blog.mmpreview.nl/uploads/ruby/Agile%20Web%20Development%20With%20Rails,%202nd%20Edition%20(2006).pdf Agiles Web Development with Rails]</li>
<li>[http://www.rubyonrails.org/docs Learn all about Ruby ob Rails]</li>
<li>http://wiki.rubyonrails.com/rails/pages/ActiveRecord</li>
</ol>


[[Kategorie:Ruby-on-Rails-HowTo]]
[[Kategorie:Ruby-on-Rails-HowTo]]
[[Kategorie:Ruby-on-Rails-Beispiel]]
[[Kategorie:Ruby-on-Rails-Beispiel]]
{{{{SITENAME}}-konformer Artikel}}
[[Kategorie:Praktikum:MMDB]]

Aktuelle Version vom 15. Oktober 2018, 14:48 Uhr

veraltet

Dieser Artikel erfüllt die GlossarWiki-Qualitätsanforderungen nur teilweise:

Korrektheit: 2
(teilweise überprüft)
Umfang: 3
(einige wichtige Fakten fehlen)
Quellenangaben: 4
(fast vollständig vorhanden)
Quellenarten: 4
(sehr gut)
Konformität: 4
(sehr gut)

Zweck

Anhand einer einfachen Web-Anwendung sollten die Prinzipien von Ruby on Rails erläutert werden.

Diesem Beispiel liegt die Händler-Datenbank zugrunde, die Wolfgang Kowarschick in der Vorlesung Datenmanagement II (Multimedia-Datenbanksysteme)[1] als Beispiel einsetzt:

Voraussetzungen

Folgende Softwarepakete wurden installiert:

Ruby on Rails wurde zusammen mit einigen Erweiterungs-Modulen mit Hilfe des Ruby-Paketmanagers gem installiert:

RUBY_DIR=...; export RUBY_DIR
POSTGRESQL_DIR=...; export  POSTGRESQL_DIR

gem install rails
gem install postgres -r -- --with-pgsql-dir=${POSTGRESQL_DIR}
gem install passenger
${RUBY_DIR}/bin/passenger-install-apache2-module

Weitere sinnvolle Pakete:

gem install rdoc
gem install mongrel
gem install ruby-debug-ide
gem install ruby-debug-base
gem install ZenTest
gem install rspec
gem install rspec-rails

Auf dem Test-Server steht in der Datei /etc/hosts ein Eintrag für den gewünschten virtuellen Server, auf dem die Anwendung laufen soll (die IP-Adresse und der Rechnernamen können natürlich angepasst werden):

192.168.0.161   haendler.kowa haendler

Diese IP-Adresse ist einem echten oder virtuellen Rechner zugeordnet. Unter SuSE 11 kann man einem Rechner ganz einfach mehrere IP-Adressen zuordnen. Man trägt die neue IP-Adresse in die Datei /etc/sysconfig/network/ifcfg-eth0 ein:

IPADDR_haendler='192.168.0.161'
NETMASK_haendler='255.255.255.0'

Danach muss der Netzwerkzugriff neu gestartet werden:

/etc/init.d/network restart

Anlegen der Händlerdatenbanken

Für jede Rails-Anwendung müssen stets drei Datenbanken existieren, eine für die Entwicklung, eine für die automatischen Tests und eine für den produktiven Betrieb. Außerdem sollte für jede Rails-Anwendung ein spezieller Benutzer definiert werden, dem diese drei Datenbanken gehören. Der Benutzer und die Datenbanken können über eine Web-Schnittstelle wie phpPgAdmin oder auch mit Hilfe von einfachen PostgreSQL-Befehlen angelegt werden:

createuser haendler -d -e -E -P -S -R

createdb -Uhaendler -Ohaendler haendler_development
createdb -Uhaendler -Ohaendler haendler_test
createdb -Uhaendler -Ohaendler haendler_production

Anlegen der Rails-Anwendung haendler

Zunächst werden im Rails-Webverzeichnis die Ordner und Dateien einer neuen Rails-Anwendung namens haendler erzeugt:

cd /web/rails
rails --database postgresql haendler

In der Konfigurations-Datei database.yml, die den Zugriff auf das DB-System regelt, wird das Passwort für den Benutzer haendler an drei Stellen eingetragen:

vi /web/rails/haendler/config/database.yml
development:
  adapter: postgresql
  encoding: unicode
  database: haendler_development
  pool: 5
  username: haendler
  password: geHEIM!?

test:
  adapter: postgresql
  encoding: unicode
  database: haendler_test
  pool: 5
  username: haendler
  password: geHEIM!?

production:
  adapter: postgresql
  encoding: unicode
  database: haendler_production
  pool: 5
  username: haendler
  password: geHEIM!?

Mit Hilfe eines rake-Kommandos kann überprüft werden, ob die Verbindung zur Datenbank hergestellt werden kann:

cd /web/rails/haendler
rake db:migrate

Wenn das rake-Kommando keinen Fehler ausgibt, ist alles in Ordnung.

Zugang zur Web-Anwendung haendler mit Hilfe von Apache

Unter der Annahme, dass Rails-Anwendungen im Verzeichnis /web/rails angelegt werden, kann der virtuelle Server in der Apache-Konfigurationsdatei httpd.conf eingetragen werden:

<VirtualHost 192.168.0.161:80>
   ServerName haendler.kowa
   DocumentRoot /web/rails/haendler/public
   <Directory /web/rails/haendler/public>
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

  CustomLog    logs/haendler-access combined
  ErrorLog     logs/haendler-error

</VirtualHost>

Nach einem Neustart von Apache mit /etc/rc.d/apache restart oder, schneller, kill -1 <Prozessnummer des Apache-Root-Prozesses> sollte man die Applikation im Browser ansehen können:

http://haendler.kowa/

Anmerkung

Wann immer an der Rails-Anwendung eine Änderung vorgenommen wurde, sollte man im tmp-Verzeichnis der Anwendung eine Datei namens restart.txt (mit beliebigem Inhalt) angelegt werden. Dadurch wird Apache informiert, dass der die Anwendung neu einlesen soll. Und somit werden alle Änderungen direkt im Browser sichtbar.

 touch /web/rails/haendler/tmp/restart.txt

Konvention: Klassennamen stehen im Singular und Datenbanktabellen-Namen im Plural

Für Ruby on Rails wurde die Konvention eingeführt, dass Klassennamen stets in Singular stehen und mit einem Großbuchstaben beginnen: Haendler, Liefert, Ware. Die zugehörigen Tabellennamen sollen dagegen im Plural stehen und kleingeschrieben werden. Die zweite Regel führt allerdings bei deutschen Tabellennamen sowie bei englischnamigen Beziehungstabellen zu unschönen Ergebnissen, wenn die Tabellen automatisch gemäß Konvention z.B. mittels ruby script/generate erzeugt werden: haendlers, lieferts, wares bzw. traders, supplies, items (hier wäre supply besser).

Wenn man die Ruby-Konvention beachten will, sollte man die falsche automatische Plural-Bildung verhindern, indem man Ausnahmen in die Datei inflections.rb einfügt:

vi /web/rails/haendler/config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
  inflect.uncountable 'haendler'
  inflect.irregular   'liefert', 'liefern'
  inflect.irregular   'ware',    'waren'
end

Anmerkung 1

Klassennamen für die Singular und Plural übereinstimmen, bereiten beim automatischen Erzeugen von Zugriffsmethoden („Scaffolding“) Probleme, da in den URLs nicht zwischen der Menge aller zugehörigen Objekten und einem einzelen Objekt unterschieden werden kann.

Diese Probleme kommen hier nicht zum Tragen, da Scaffolding sowieso nicht verwendet wird.

Anmerkung 2

In allen meinen Programmen und Vorlesungsbeispielen halte ich mich normalerweise an die Konvention, dass sowohl Klassen-, als auch Tabellennamen im Singular geschrieben werden sollten. Dies könnte man in Ruby on Rails erreichen, indem man eine Initialisierungs-Datei pluralize_false.rb mit folgenden Inhalt anlegt:

# vi /web/rails/haendler/config/initializers/pluralize_false.rb
ActiveRecord::Base.pluralize_table_names = false

Das es nun einmal Ruby-on-Rails-Konvention ist, Tabellennamen im Plural zu schreiben, greife ich in diesem Beispiel nicht auf diese Möglichkeit zurück.

Anlegen des Modells

Anlegen der Modell-Dateien

Mit Hilfe des Ruby-Skriptes ruby script/generate model kann nun die Dateien des Modells, d.h. die Dateien für die Klassen- und Tabellen-Definitionen des Modells angelegt werden.

cd /web/rails/haendler/
ruby script/generate model haendler
ruby script/generate model ware
ruby script/generate model liefert

Ergebnis:

exists  app/models/
exists  test/unit/
exists  test/fixtures/
create  app/models/haendler.rb
create  test/unit/haendler_test.rb
create  test/fixtures/haendlers.yml
create  db/migrate
create  db/migrate/20081208135556_create_haendlers.rb

...

Primär- und Fremdschlüssel

Laut Ruby-on-Rails-Philosophie sollte jede Tabelle ein Primärschlüssel-Attribut namens id enthalten. Dieses wird, wenn man in den zugehörigen Migrate-Dateien nichts anderes angibt, mittels

rake db:migrate

automatisch erzeugt.

Nun benötigen Beziehungstabellen kein künstliches Schlüssel-Attribut. Eine übliche Methode ist es daher, in Rails-Anwendungen bei Beziehungstabellen ganz auf Primärschlüssel zu verzichten und anstatt dessen einen Index für die Beziehungsattribute zu definieren (siehe z.B. den Abschnitt "Migration" in der Rails-Kurz-Referenz von InVisible[2]). Sollte eine Beziehungstabelle weitere Attribute haben, so müsste für die Beziehungsattribute zusätzlich noch eine gemeinsame Unique-Bedingung angegeben werden. Dies wird für mehr als ein Attribut von Rails leider auch nicht direkt unterstüzt (vgl. z.B. die Dokumentation zur Klasse ActiveRecord::Migration[3], Abschnitt „More examples“).

In einem sehr schönen Blog-Beitrag[4] beschreibt Jeff Smith, warum im Falle von Beziehungstabellen „Composite Primary Keys“, wie Multi-Attribut-Primärschlüssel im Englischen genannt werden, so wichtig sind. Er widerlegt dabei auch in einer Antwort auf eine Leserzuschrift die Argumente von Lee Richardson[5], der gegen „Composite Primary Keys“ argumentiert.

Ruby on Rails unterstützt nicht nur die Angabe von Multi-Attribut-Primärschlüsseln nicht, auch die Angabe von Fremdschlüsseln wird nicht direkt unterstützt.

Glücklicherweise gibt es zwei Erweiterungspakete, die diese Probleme zumindest teilweise beheben.

Die Arbeit mit Multi-Primärschlüsseln ermöglicht das Rails-Paket composite_primary_keys. Und mit Hilfe des Pakets foreign_key_migrations können auch Foreign Keys innerhalb von Migration-Dateien definiert werden:

gem install composite_primary_keys
gem install foreign_key_migrations

Um die beiden Pakete verwenden zu können, müssen sie in der Händler-Anwendung noch aktiviert werden:

vi /web/rails/haendler/config/initializers/db_extensions.rb
require 'composite_primary_keys'
require 'composite_primary_keys/migration'
require 'foreign_key_migrations'

Beide Pakete sorgen jedoch leider nicht dafür, dass Multi-Primärschlüssel und Foreign Keys auch in der automatisch generierten Datei schema.db aufgeführt werden (siehe Abschnitt „Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen“).

ActiveRecord-PostgreSQL-Patch

Wenn man mit Fremdschlüssel arbeitet, stolpert man ziemlich schnell über einen Rails-PostgreSQL-Bug[6]. Diesen kann man mit folgendem Patch beheben:

vi /web/rails/haendler/config/initializers/active_record_postgresql.rb
module ActiveRecord
  module ConnectionAdapters
    class PostgreSQLAdapter < AbstractAdapter
      def disable_referential_integrity(&block)
        transaction {
          begin
            execute "SET CONSTRAINTS ALL DEFERRED"
            yield
          ensure
            execute "SET CONSTRAINTS ALL IMMEDIATE"
          end
        }
      end
    end
  end
end

Bearbeiten der Migrate-Dateien

Nun werden die Attribute, Schlüssel, Fremdschlüssel und Indexe der einzelnen Klassen des Modells in den zugehörigen Migrate-Dateien eingetragen.

vi /web/rails/haendler/db/migrate/*_create_haendler.rb
class CreateHaendler < ActiveRecord::Migration
  def self.up
    create_table :haendler do |t|
      t.string :name,   :null => false
      t.string :adresse

      t.timestamps
    end
  end

  def self.down
    remove_index :liefern, [:ware_id]
    drop_table :haendler
  end
end
vi /web/rails/haendler/db/migrate/*_create_waren.rb
class CreateWaren < ActiveRecord::Migration
  def self.up
    create_table :waren do |t|
      t.string :typ,         :null => false
      t.string :bezeichnung, :null => false

      t.timestamps
    end

    execute "ALTER TABLE waren ADD CONSTRAINT unique_typ_bezeichnung UNIQUE (typ, bezeichnung)"
  end

  def self.down
    drop_table :waren
  end
end


vi /web/rails/haendler/db/migrate/*_create_liefern.rb
class CreateLiefern < ActiveRecord::Migration
  def self.up
    create_table :liefern, :primary_key => [:haendler_id, :ware_id, :preis] do |t|
      t.integer :haendler_id, :null => false, :references => :haendler
      t.integer :ware_id,     :null => false, :references => :waren
      t.decimal :preis,       :null => false, :precision => 6, :scale => 2
      t.integer :lieferzeit

      t.timestamps
    end

    add_index :liefern, [:ware_id]
  end

  def self.down
    drop_table :liefern
  end
end

Anschließend werden die Tabellen mit dem Ruby-Make-Befehl rake erzeugt:

cd /web/rails/haendler
rake db:migrate
rake db:migrate RAILS_ENV="production"

Anmerkung

Mit

rake db:migrate                        VERSION="0"
rake db:migrate RAILS_ENV="production" VERSION="0"

können alle Tabellen wieder gelöscht werden.

Bearbeitung der Klassen des Modells

Die Beziehungen und sonstigen Eigenschaften der Modell-Klassen sollten in den durch ruby script/generate model angelegten Modell-Dateien angegeben werden. Dadurch ist es möglich, auf die Daten in der Datenbank zuzugreifen, ohne SQL-Code schreiben zu müssen.

vi /web/rails/haendler/app/models/haendler.rb
class Haendler < ActiveRecord::Base
  has_many :waren, :through => :liefern
  has_many :liefern

  validates_presence_of :name
end
vi /web/rails/haendler/app/models/ware.rb
class Ware < ActiveRecord::Base
  has_many :haendler, :through => :liefern
  has_many :liefern

  validates_presence_of :typ, :bezeichnung
end
vi /web/rails/haendler/app/models/liefert.rb
class Liefert < ActiveRecord::Base
  belongs_to :haendler, :foreign_key => :haendler_id
  belongs_to :waren,    :foreign_key => :ware_id

  set_primary_keys [:haendler_id, :ware_id, :preis]
  validates_presence_of :haendler_id, :ware_id, :preis
end

Anschließend sollte mit einem Browser Ihrer Wahl überprüft werden, ob irgendwelche Fehlermeldungen ausgegeben werden:

touch /web/rails/haendler/tmp/restart.txt
lynx http://haendler.kowa/test

Wenn der Server mit der Fehlermeldung 404 anzeigt, dass es diese Seite nicht gibt, ist alles OK. Views wurden ja noch nicht definiert. Wenn der Server allerdings mit der Fehlermeldung 500 anzeigt, dass ein interner Server-Fehler vorliegt, wurden die Modell-Dateien nicht korrekt definiert. Der Server gibt dann auch eine genauere Fehlermeldung aus.

Anlegen der Testdaten

In Rails sollten für jede Anwendung Tests generiert werden, um die Integrität der Anwendung jederzeit auotmatisch testen zu können. Insbesondere müssen Test-Daten definiert werden, mit denen bei Start eines Test die Test-Datenbank initialisiert wird.

vi /web/rails/haendler/test/fixtures/haendler.yml
maier_koenigsbrunn:
  id:      1
  name:    Maier
  adresse: Königsbrunn

mueller_koenigsbrunn:
  id:      2
  name:    Müller
  adresse: Königsbrunn

maier_augsburg:
  id:      3
  name:    Maier
  adresse: Augsburg

huber:
  id:      4
  name:    Huber
vi /web/rails/haendler/test/fixtures/waren.yml
cup1:
  id:          1
  typ:         CPU
  bezeichnung: Pentium IV 3,8

cpu2:
  id:      2
  typ:         CPU
  bezeichnung: Celeron 2,6'

cpu3:
  id:      3
  typ:         CPU
  bezeichnung: Athlon XP 3000+

eieruhr:
  id:      4
  typ:         Sonstiges
  bezeichnung: Eieruhr
vi /web/rails/haendler/test/fixtures/liefern.yml
mak_c1:
  haendler_id:      1
  ware_id:          1
  preis:       200.00
  lieferzeit:       1

mak_c2:
  haendler_id:      1
  ware_id:          2
  preis:       100.00
  lieferzeit:       1

mak_c3:
  haendler_id:      1
  ware_id:          3
  preis:       150.00

mak_3:
  haendler_id:      1
  ware_id:          4
  preis:        10.00
  lieferzeit:       1

muk_cp1:
  haendler_id:      2
  ware_id:          1
  preis:       160.00
  lieferzeit:       1

muk_cp2:
  haendler_id:      2
  ware_id:          2
  preis:       180.00

muk_cp3:
  haendler_id:      2
  ware_id:          3
  preis:       150.00
  lieferzeit:       4

maa_cp1:
  haendler_id:      3
  ware_id:          1
  preis:       160.00
  lieferzeit:       4

maa_cp2:
  haendler_id:      3
  ware_id:          2
  preis:       190.00
  lieferzeit:       1

hu_cp1:
  haendler_id:      4
  ware_id:          1
  preis:       150.00
  lieferzeit:       4

hu_cp3:
  haendler_id:      4
  ware_id:          3
  preis:       180.00
  lieferzeit:       5

hu_cp3_2:
  haendler_id:      4
  ware_id:          3
  preis:       199.00
  lieferzeit:       1

Ob alle Daten korrekt eingetragen wurden, kann man überprüfen, indem man eine Rails-Testlauf startet. Dies geschieht einfach durch Aufruf des Befehls rake. Daran, dass das Testen die Default-Aktion von rake ist, erkennt man die Bedeutung, die die Rails-Gemeinde dem Testen beimisst.

cd /web/rails/haendler
rake

Testen des Modells

Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen

Wenn man die Test-Tabellen mit db:migrate erzeugt, werden alle Primär- und Fremdschlüssel sowie die Integritätsbedingung unique_typ_bezeichnung wie gewünscht in der Test-Datenbank angelegt:

rake db:migrate RAILS_ENV="test" VERSION=0
rake db:migrate RAILS_ENV="test"

Erzeugt man die Test-Datenbank hingegen mit

rake

oder

rake db:test:prepare

fehlen die mit Hilfe der gem-Pakete composite_primary_keys und foreign_key_migrations angelegten Primär- und Fremdschlüssel sowie der Index unique_typ_bezeichnung.

Dies ist ziemlich nachteilig, da sich die Datenbank-Struktur der Test-Datenbank nicht von der Datenbankstruktur der beiden anderen Datenbanken unterscheiden sollte.

Der Grund für dieses Verhalten ist, dass anstelle der Migrations-Dateien die Datei /web/rails/haendler/db/schema.db zum Erzeugen der Test-Datenbank verwendet wird. Diese Datei wird bei jedem Aufruf von rake db:migrate automatisch erzeugt. Leider werden dabei die Primär- und Fremdschlüssel nicht korrekt übernommen.

Dieses Problem kann derzeit am einfachsten behoben werden, indem man eine Datei schema_sound.db von Hand erzeugt, die die korrekten Schema-Definitionen enthält. Jedesmal, wenn die Datei schema.db neu generiert wird, sollte man diese Datei mit der Datei schema_sound.db überschreiben. Eventuell muss zuvor die Datei schema_sound.db gemäß den an den Migrations-Dateien vorgenommenen Änderungen ebenfalls modifiziert werden.

vi /web/rails/haendler/db/schema_sound.rb
# Die Versionsnummer sollte aus der aktuellen schema.db übernommen werden.
ActiveRecord::Schema.define(:version => 20081216130913) do

  create_table "haendler", :force => true do |t|
    t.string "name",    :null => false
    t.string "adresse"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "waren", :force => true do |t|
    t.string "typ",         :null => false
    t.string "bezeichnung", :null => false
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  execute "ALTER TABLE waren ADD CONSTRAINT unique_typ_bezeichnung UNIQUE (typ, bezeichnung)"

  create_table "liefern", :primary_key => [:haendler_id, :ware_id, :preis], :force => true  do |t|
    t.integer "haendler_id",                               :null => false, :references => :haendler
    t.integer "ware_id",                                   :null => false, :references => :waren
    t.decimal "preis",       :precision => 6, :scale => 2, :null => false
    t.integer "lieferzeit"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "liefern", ["ware_id"], :name => "index_liefern_on_ware_id"

end

Anlegen der Tests

Beim Testen der Anwendung werden die Test-Methoden aller Dateien, die in einem der Ordner

  • test/unit (Modell-Tests)
  • test/functional (Controller- und View-Tests)
  • test/integration (Test zur Interaktion zwischen Benutzer und Anwendung)
  • test/performance (Performanz-Tests)

gefunden werden und deren Namen mit _test enden, der Reihe nach durchgeführt. Jede dieser Test-Methoden soll mit Hilfe der Methoden assert, assert_generates etc. sicherstellen, dass bestimmte Integritätsbedingungen von der Anwendung nicht verletzt werden.

Zum Beispiel kann man überprüfen, ob der Primär- und die Fremdschlüssel der Tabelle liefern korrekt beachtet werden:

vi /wkcms/web/rails/haendler/test/unit/liefert_test.rb
require 'test_helper'

class LiefertTest < ActiveSupport::TestCase
  test "foreign key haendler" do
    liefert1 = Liefert.new(:haendler_id => 6,
                           :ware_id     => 1,
                           :preis       => 199.00
                          )
    assert liefert1.valid?

    result1 = false 
    begin
      liefert1.save
    rescue
      result1 = true # Händler 6 existiert nicht.
    end
    assert result1
  end

  test "foreign key waren" do
    liefert2 = Liefert.new(:haendler_id => 1,
                           :ware_id     => 7,
                           :preis       => 399.00
                          )
    assert liefert2.valid?

    result2 = false
    begin
      liefert2.save
    rescue
      result2 = true # Ware 7 existiert nicht.
    end
    assert result2
  end

  test "primary key" do
    liefert = Liefert.new(:haendler_id => 1,
                          :ware_id     => 1,
                          :preis       => 200.00,
                          :lieferzeit  => 5
                         )
    assert liefert.valid?

    result = false
    begin
      liefert.save
    rescue
      result = true # Es gibt schon einen Eintrag in der Datenbank,
                    # dass Händler 1 die Ware 1 zum Preis von 200.00
                    # innerhalb eines Tages liefert.
    end
    assert result
  end

end

Durchführen der Tests

Wenn man nun die Tests mit der originalen schema.db durchführt, erhält man drei Assert-Fehler:

cd /web/rails/haendler
rake

> 5 tests, 8 assertions, 3 failures, 0 errors

Der Grund ist, dass alle drei Objekte, die in der Datei liefert_test.rb definiert werden, problemlos in die Test-Datenbank eingefügt werden können. Das heißt, der Primär- und die Fremdschlüssel werden nicht beachtet.

Um dies zu erreichen, muss man die falsche Datei schema.db durch die richtige ersetzen.

cp /web/rails/haendler/db/schema_sound.rb /web/rails/haendler/db/schema.rb

Allerdings funktioniert der Test jetzt immer noch nicht, sondern endet nun sogar mit fünf Laufzeit-Fehlern. Der Grund ist, dass die Datenbank-Tabellen in alphabetischer Reihenfolge gefüllt werden. Aufgrund der Fremdschlüssel muss aber die Tabelle liefern als letzte gefüllt werden. Die erreicht man, in dem man die Reihenfolge der drei Tabellen explizit in der Datei test_helper.rb angibt:

vi /web/rails/haendler/test/test_helper.rb
 #fixtures :all  ## :all bedeutet "Initialisierung in alphabetischer Reihenfolge"
  fixtures :haendler, :waren, :liefern

Nun sollten alle Tests fehlerfrei funktionieren:

cd /web/rails/haendler
rake

> 5 tests, 8 assertions, 0 failures, 0 errors

Das gem-Paket ZenTest ermöglicht automatische Test. Sobald man

cd /web/rails/haendler
autotest

eingegeben hat, werden nach jeder Änderung der Anwendun die Tests automatisch gestartet.

Anlegen der Controller- und der View-Dateien

Scaffolding

In diesem Beispiel werde ich auf Scaffolding verzichten.

Mit Hilfe des Scaffold-Skriptes könnten ein paar Controller- und View-Dateien angelegt werden, um Waren erfassen, bearbeiten und löschen zu können.

cd /web/rails/haendler
ruby script/generate scaffold ware typ:string bezeichnung:string

touch /web/rails/haendler/tmp/restart.txt
lynx http://haendler.kowa/waren/

Leider funktioniert diese Methode nicht problemlos für die Klasse Haendler, da hier „Singular gleich Plural“ gilt.

Erzeugung von einigen Controller- und View-Dateien

Bislang können die Inhalte der Datenbank weder im Web präsentiert werden, noch ist es möglich, sie über ein Web-Interface zu modifizieren. Um dies zu erreichen brauchen man View-Templates (für die Präsentation) und Controller-Klassen (für die Verarbeitung der Benutzeraktionen).

Zunächst sollten die wichtigsten Dateien automatisch erzeugt werden:

cd /web/rails/haendler

ruby script/generate rspec
ruby script/generate controller Haendler index
ruby script/generate controller Ware     index

Quellen

  1. Kowarschick (CMS): Wolfgang Kowarschick; Vorlesung „Content-Management“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2012; Quellengüte: 3 (Vorlesung)
  2. Agiles Web Development with Rails
  3. Learn all about Ruby ob Rails
  4. http://wiki.rubyonrails.com/rails/pages/ActiveRecord