Händler-Anwendung (Ruby-on-Rails-Beispiel): Unterschied zwischen den Versionen
Kowa (Diskussion | Beiträge) |
Kowa (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
(25 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
{{ | '''veraltet''' | ||
{{Qualität | |||
= Zweck = | |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. | ||
Diesem Beispiel liegt die Händler-Datenbank zugrunde, die [[Benutzer:Kowa|Wolfgang Kowarschick]] in der Vorlesung Datenmanagement II (Multimedia-Datenbanksysteme)<ref> | 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 = | == Voraussetzungen == | ||
Folgende Softwarepakete wurden installiert: | Folgende Softwarepakete wurden installiert: | ||
Zeile 23: | Zeile 29: | ||
POSTGRESQL_DIR=...; export POSTGRESQL_DIR | POSTGRESQL_DIR=...; export POSTGRESQL_DIR | ||
gem install rails | 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> | ||
Zeile 52: | Zeile 69: | ||
</source> | </source> | ||
= Anlegen der Händlerdatenbanken = | == Anlegen der Händlerdatenbanken == | ||
Für jede Rails-Anwendung müssen stets drei Datenbanken existieren, eine für die Entwicklung, eine | Für jede Rails-Anwendung müssen stets drei Datenbanken existieren, eine für die Entwicklung, eine | ||
Zeile 68: | Zeile 85: | ||
</source> | </source> | ||
= Anlegen der Rails-Anwendung <code>haendler</code>= | == 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: | Zunächst werden im Rails-Webverzeichnis die Ordner und Dateien einer neuen Rails-Anwendung namens <code>haendler</code> erzeugt: | ||
Zeile 118: | Zeile 135: | ||
Wenn das <code>rake</code>-Kommando keinen Fehler ausgibt, ist alles in Ordnung. | Wenn das <code>rake</code>-Kommando keinen Fehler ausgibt, ist alles in Ordnung. | ||
= Zugang zur Web-Anwendung <code>haendler</code> mit Hilfe von Apache = | == Zugang zur Web-Anwendung <code>haendler</code> mit Hilfe von Apache == | ||
Unter der Annahme, dass Rails-Anwendungen im Verzeichnis <code>/web/rails</code> | Unter der Annahme, dass Rails-Anwendungen im Verzeichnis <code>/web/rails</code> | ||
Zeile 148: | Zeile 165: | ||
</source> | </source> | ||
==Anmerkung== | ===Anmerkung=== | ||
Wann immer an der Rails-Anwendung eine Änderung vorgenommen wurde, sollte man im <code>tmp</code>-Verzeichnis | 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 | der Anwendung eine Datei namens <code>restart.txt</code> (mit beliebigem Inhalt) angelegt werden. Dadurch wird | ||
Zeile 157: | Zeile 174: | ||
</source> | </source> | ||
= Konvention: Klassennamen stehen im Singular und Datenbanktabellen-Namen im Plural= | == 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>, | 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>, | ||
Zeile 179: | Zeile 196: | ||
</source> | </source> | ||
=== Anmerkung 1 === | ==== Anmerkung 1 ==== | ||
Klassennamen für die Singular und Plural übereinstimmen, | Klassennamen für die Singular und Plural übereinstimmen, | ||
Zeile 187: | Zeile 204: | ||
Diese Probleme kommen hier nicht zum Tragen, da Scaffolding sowieso nicht verwendet wird. | Diese Probleme kommen hier nicht zum Tragen, da Scaffolding sowieso nicht verwendet wird. | ||
=== Anmerkung 2 === | ==== 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> | 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> | ||
Zeile 201: | Zeile 218: | ||
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. | 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 des Modells == | ||
== Anlegen der Modell-Dateien == | === 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. | 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. | ||
Zeile 227: | Zeile 244: | ||
</source> | </source> | ||
== Primär- und Fremdschlüssel == | === Primär- und Fremdschlüssel === | ||
Laut Ruby-on-Rails-Philosophie sollte jede Tabelle ein Primärschlüssel-Attribut namens <code>id</code> enthalten. | Laut Ruby-on-Rails-Philosophie sollte jede Tabelle ein Primärschlüssel-Attribut namens <code>id</code> enthalten. | ||
Zeile 273: | Zeile 290: | ||
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“]]). | 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 === | ==== 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: | 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"> | <source lang="bash"> | ||
vi | vi /web/rails/haendler/config/initializers/active_record_postgresql.rb | ||
</source> | </source> | ||
<source lang="ruby"> | <source lang="ruby"> | ||
Zeile 286: | Zeile 303: | ||
def disable_referential_integrity(&block) | def disable_referential_integrity(&block) | ||
transaction { | transaction { | ||
begin | |||
execute "SET CONSTRAINTS ALL DEFERRED" | |||
yield | |||
ensure | |||
execute "SET CONSTRAINTS ALL IMMEDIATE" | |||
end | |||
} | } | ||
end | end | ||
Zeile 299: | Zeile 316: | ||
</source> | </source> | ||
== Bearbeiten der Migrate-Dateien == | === Bearbeiten der Migrate-Dateien === | ||
Nun werden die Attribute, Schlüssel, Fremdschlüssel und Indexe der einzelnen Klassen des Modells | Nun werden die Attribute, Schlüssel, Fremdschlüssel und Indexe der einzelnen Klassen des Modells | ||
Zeile 325: | Zeile 342: | ||
end | end | ||
</source> | </source> | ||
<source lang="bash"> | <source lang="bash"> | ||
Zeile 384: | Zeile 400: | ||
</source> | </source> | ||
===Anmerkung=== | ====Anmerkung==== | ||
Mit | Mit | ||
Zeile 410: | Zeile 426: | ||
end | end | ||
</source> | </source> | ||
<source lang="bash"> | <source lang="bash"> | ||
Zeile 423: | Zeile 438: | ||
end | end | ||
</source> | </source> | ||
<source lang="bash"> | <source lang="bash"> | ||
Zeile 450: | Zeile 464: | ||
definiert. Der Server gibt dann auch eine genauere Fehlermeldung aus. | definiert. Der Server gibt dann auch eine genauere Fehlermeldung aus. | ||
== Anlegen der Testdaten == | === Anlegen der Testdaten === | ||
In Rails sollten für jede Anwendung Tests generiert werden, um die Integrität der Anwendung jederzeit | In Rails sollten für jede Anwendung Tests generiert werden, um die Integrität der Anwendung jederzeit | ||
Zeile 479: | Zeile 493: | ||
name: Huber | name: Huber | ||
</source> | </source> | ||
<source lang="bash"> | <source lang="bash"> | ||
Zeile 505: | Zeile 518: | ||
bezeichnung: Eieruhr | bezeichnung: Eieruhr | ||
</source> | </source> | ||
<source lang="bash"> | <source lang="bash"> | ||
Zeile 592: | Zeile 604: | ||
</source> | </source> | ||
== Testen des Modells == | === Testen des Modells === | ||
=== Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen === | ==== Probleme mit Primär- und Fremdschlüsseln sowie Unique-Attributen ==== | ||
Wenn man die Test-Tabellen mit <code>db:migrate</code> erzeugt, | Wenn man die Test-Tabellen mit <code>db:migrate</code> erzeugt, | ||
Zeile 623: | Zeile 635: | ||
Leider werden dabei die Primär- und Fremdschlüssel nicht korrekt übernommen. | Leider werden dabei die Primär- und Fremdschlüssel nicht korrekt übernommen. | ||
Dieses Problem kann derzeit | 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 | 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 | Datei <code>schema.db</code> neu generiert wird, sollte man diese Datei mit | ||
Zeile 631: | Zeile 643: | ||
<source lang="bash"> | <source lang="bash"> | ||
vi /web/rails/haendler/db/schema_sound. | vi /web/rails/haendler/db/schema_sound.rb | ||
</source> | </source> | ||
<source lang="ruby"> | <source lang="ruby"> | ||
Zeile 640: | Zeile 652: | ||
t.string "name", :null => false | t.string "name", :null => false | ||
t.string "adresse" | t.string "adresse" | ||
t.datetime "created_at" | |||
t.datetime "updated_at" | |||
end | end | ||
Zeile 645: | Zeile 659: | ||
t.string "typ", :null => false | t.string "typ", :null => false | ||
t.string "bezeichnung", :null => false | t.string "bezeichnung", :null => false | ||
t.datetime "created_at" | |||
t.datetime "updated_at" | |||
end | end | ||
execute "ALTER TABLE waren ADD CONSTRAINT unique_typ_bezeichnung UNIQUE (typ, bezeichnung)" | 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| | create_table "liefern", :primary_key => [:haendler_id, :ware_id, :preis], :force => true do |t| | ||
t.integer "haendler_id", :null => false, :references => :haendler | t.integer "haendler_id", :null => false, :references => :haendler | ||
Zeile 656: | Zeile 670: | ||
t.decimal "preis", :precision => 6, :scale => 2, :null => false | t.decimal "preis", :precision => 6, :scale => 2, :null => false | ||
t.integer "lieferzeit" | t.integer "lieferzeit" | ||
t.datetime "created_at" | |||
t.datetime "updated_at" | |||
end | end | ||
Zeile 663: | Zeile 679: | ||
</source> | </source> | ||
=== Anlegen der Tests === | ==== Anlegen der Tests ==== | ||
Beim Testen der Anwendung werden die Test-Methoden aller Dateien, die in einem der Ordner | Beim Testen der Anwendung werden die Test-Methoden aller Dateien, die in einem der Ordner | ||
Zeile 740: | Zeile 756: | ||
</source> | </source> | ||
=== Durchführen der Tests === | ==== Durchführen der Tests ==== | ||
Wenn man nun die Tests mit der originalen <code>schema.db</code> durchführt, erhält man drei Assert-Fehler: | Wenn man nun die Tests mit der originalen <code>schema.db</code> durchführt, erhält man drei Assert-Fehler: | ||
Zeile 786: | Zeile 802: | ||
</source> | </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 == | |||
= Anlegen der Controller-Dateien = | === Scaffolding === | ||
== Scaffolding == | |||
'''In diesem Beispiel werde ich auf Scaffolding verzichten.''' | '''In diesem Beispiel werde ich auf Scaffolding verzichten.''' | ||
Zeile 807: | Zeile 829: | ||
da hier „Singular gleich Plural“ gilt. | 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> | |||
=Quellen= | ==Quellen== | ||
<references /> | <references /> | ||
<ol> | <ol> | ||
<li value="8"> | <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://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://www.rubyonrails.org/docs Learn all about Ruby ob Rails]</li> | ||
Zeile 821: | Zeile 858: | ||
[[Kategorie:Ruby-on-Rails-HowTo]] | [[Kategorie:Ruby-on-Rails-HowTo]] | ||
[[Kategorie:Ruby-on-Rails-Beispiel]] | [[Kategorie:Ruby-on-Rails-Beispiel]] | ||
[[Kategorie:Praktikum:MMDB]] |
Aktuelle Version vom 15. Oktober 2018, 16: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:
- Apache 2.2.x
- PostgreSQL 8.x (am Besten zusammen mit einem Frontend wie z.B phpPgAdmin)
- Ruby 1.8.7
- Ruby on Rails 2.2
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
- ↑ Kowarschick (MMDB-Skript): Wolfgang Kowarschick; Vorlesung Multimedia-Datenbanksysteme – Sommersemester 2018; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2018; Quellengüte: 4 (Skript)
- ↑ http://blog.invisible.ch/files/rails-reference-1.1.html#migration
- ↑ http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
- ↑ http://weblogs.sqlteam.com/jeffs/archive/2007/08/23/composite_primary_keys.aspx
- ↑ http://rapidapplicationdevelopment.blogspot.com/2007/08/in-case-youre-new-to-series-ive.html
- ↑ http://kopongo.com/2008/7/25/postgres-ri_constrainttrigger-error
- Kowarschick (CMS): Wolfgang Kowarschick; Vorlesung „Content-Management“; Hochschule: Hochschule Augsburg; Adresse: Augsburg; Web-Link; 2012; Quellengüte: 3 (Vorlesung)
- Agiles Web Development with Rails
- Learn all about Ruby ob Rails
- http://wiki.rubyonrails.com/rails/pages/ActiveRecord