14.3 RESTful Rails 

Vorteile
Der Einsatz des REST-Standards in Rails wird als RESTful Rails bezeichnet.
Wenn Sie Ihre Applikationen nach dem REST-Standard entwickeln, bietet das u. a. folgende Vorteile:
- URLs sind vereinfacht.
- URLs sind standardisiert.
- Realisierung eines Webservices bzw. einer API ist praktisch ohne Mehraufwand möglich.
Rails macht es Ihnen sehr leicht, Websites nach dem REST-Standard zu erstellen. Am einfachsten ist es, wenn Sie Generatoren einsetzen.
Generatoren 

Es gibt zwei Generatoren, die Sie beim Implementieren des REST-Standards unterstützen:
- scaffold-Generator
Der scaffold-Generator erzeugt neben einem Model und der Migration- Datei für die Erstellung der Datenbanktabelle auch einen Controller mit sieben Actions und die passenden Views, um die Datensätze zu verwalten (Anzeigen, Hinzufügen, Ändern und Löschen).ruby script/generate scaffold airport name:string \ code:string ... create app/views/airports/index.html.erb create app/views/airports/show.html.erb create app/views/airports/new.html.erb create app/views/airports/edit.html.erb create app/views/layouts/airports.html.erb create public/stylesheets/scaffold.css create app/models/airport.rb create test/unit/airport_test.rb create test/fixtures/airports.yml create db/migrate create db/migrate/001_create_airports.rb create app/controllers/airports_controller.rb create test/functional/airports_controller_test.rb create app/helpers/airports_helper.rb route map.resources :airports
- resource-Generator
Der resource -Generator macht im Prinzip das Gleiche wie der scaffold -Generator, jedoch mit dem Unterschied, dass keine Views erstellt werden und der Controller keine Actions (Methoden) enthält.
ruby script/generate resource airport name:string \ code:string ... create app/views/airports create app/models/airport.rb create test/unit/airport_test.rb create test/fixtures/airports.yml create db/migrate create db/migrate/001_create_airports.rb create app/controllers/airports_controller.rb create test/functional/airports_controller_test.rb create app/helpers/airports_helper.rb route map.resources :airports
Ressourcen 

routes.rb
Beide Generatoren fügen in der Routing-Konfigurationsdatei routes.rb im Verzeichnis config folgenden Eintrag ein:
Listing config/routes.rb
map.resources :airports
Aufgrund dieses Routing-Eintrages sind folgende Zugriffe auf die Ressource airports möglich:
- GET /airports
Es wird der AirportsController mit der Action index aufgerufen. Diese Action listet alle Flughäfen. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Alle Airports', airports_path+
- GET /airports/1
Es wird der AirportsController mit der Action show und dem Parameter id = 1 aufgerufen. Diese Action zeigt den Flughafen mit der angegebenen ID an. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Airport Detail', airport_path(1)
- GET /airports/new
Es wird der AirportsController mit der Action new aufgerufen. Diese Action lädt das Formular zum Anlegen eines Airports. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'neuer Airport', new_airport_path
- GET /airports/1/edit
Es wird der AirportsController mit der Action edit aufgerufen. Diese Action lädt das Formular zum Bearbeiten eines Airports. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Airport editieren', edit_airport_path(1)
- POST /airports
Es wird der AirportsController mit der Action create aufgerufen. Diese Action erstellt einen neuen Airport-Datensatz. Der Aufruf erfolgt über ein Formular:Die HTTP-Methode muss nicht angegeben werden, da sie automatisch erkannt wird. Wenn das Objekt @airport neu ist, wird die POST-Methode verwendet.form_for(@airport) do ... end
- PUT /airports/1
Es wird der AirportsController mit der Action update aufgerufen. Diese Action ändert die Daten des angegebenen Airport-Datensatzes. Der Aufruf erfolgt über ein Formular:Die HTTP-Methode muss nicht angegeben werden, da sie automatisch erkannt wird. Wenn das Objekt @airport bereits in der Datenbank vorliegt, wird automatisch die PUT-Methode ausgeführt.form_for(@airport) do ... end
- DELETE /airports/1
Es wird der AirportsController mit der Action destroy aufgerufen. Diese Action löscht den angegebenen Datensatz. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to "löschen", airport_path(1), :method => :delete
Zugriff auf ID
Da per Konvention immer auf die ID eines Objektes zugegriffen wird, wenn das ganze Objekt übergeben wird, können wir statt der IDs auch das Objekt airport in die Methoden übergeben:
airport_path(airport) edit_airport_path(airport)
Standard-Routing deaktivieren Wenn Sie nur Ressourcen einsetzen, ist es empfehlenswert, das Standard-Routing zu deaktivieren. Kommentieren Sie dazu die folgenden Zeilen aus der config/routes.rb aus: map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'
Verschachtelte Ressourcen
Wenn eine Ressource von einer anderen Ressource abhängig ist, kann eine verschachtelte Ressource angelegt werden. Angenommen, wir wollten zu den Flughäfen die Ladenlokale (shops) verwalten, können wir mit dem scaffold -Generator eine Model-Klasse mit zugehörigem Controller und Formularen erstellen:
ruby script/generator scaffold shop name:string ...
Da es zu einem Flughafen mehrere Shops geben kann, besteht eine Eins-zu-viele-Relation(siehe Kapitel 10). Interessant ist, dass diese Abhängigkeit auch in der URL abgebildet werden kann.
has_many
Dazu wird die Abhängigkeit wie folgt im Routing festgelegt:
Listing config/routes.rb
map.resources :airports, :has_many => :shops
Der Zugriff auf die Ressource shops erfolgt dann wie folgt:
- GET http://localhost:3000/airports/2
Es wird im Airports-Controller die Action show mit dem Parameter id=2 aufgerufen. Im View kann der Aufruf wie folgt erfolgen:link_to 'Airport Detail', airport_path(2)
- GET http://localhost:3000/airports/2/shops
Es wird im Shops-Controller die Action index mit dem Parameter airport_id=2 aufgerufen. Im View kann der Aufruf wie folgt erfolgen:link_to 'Alle Shops vom Flughafen 2',
airport_shops_path(:airport_id=>2)
- DELETE http://localhost:3000/airports/2/shops/5
Es wird im Shops-Controller die Action destroy mit den Parametern id=5 und airport_id=2 aufgerufen. Im View kann der Aufruf wie folgt erfolgen:link_to 'Zeige Shop 5 vom Flughafen 2',
airport_shop_path(:airport_id=>2, :id=>5)
- usw.
Mehrere Verschachtelungen
Es können in einem Routing-Eintrag auch mehrere Verschachtelungen definiert werden. Ein Flughafen kann nicht nur mehrere Shops, sondern auch mehrere Restaurants haben. Der Routing-Eintrag könnte dann wie folgt aussehen:
map.resources :airports, :has_many => [:shops, :restaurants]
has_one
Eine Abhängigkeit mit einer Singleton-Ressource (siehe Abschnitt 14.3.5) kann mit der :has_one -Option definiert werden. In der folgenden Routing-Regel wird jedem Airport ein Direktor zugeordnet:
map.Ressource :airports, :has_one => :director
Namespaces
Rails 2.0
Namespaces ist eine neue Funktion von Rails 2.0, die in der Praxis eine hohe Relevanz hat.
Normalerweise gehört zu einer Model-Klasse (z. B. Airport) genau einen Controller (Airports-Controller). Dieser Controller dient dann in der Regel zum Verwalten der Daten. Man kann auch sagen, dass dieser Controller für die Administratoren (oder Redakteure) der Website bestimmt ist. Für die Anzeige der Flughäfen für den Endbenutzer ist ein weiterer Controller erforderlich, der sich auf das gleiche Model bezieht.
Die beiden Controller sollten wie folgt aufgerufen werden können:
- http://localhost:3000/admin/airports
Auf dieser Seite können die Flughäfen verwaltet werden. Es werden alle Flughäfen angezeigt. Zu jedem Datensatz werden Links zum Ändern und Löschen angezeigt. Diese Seite sollte passwortgeschützt sein.
- http://localhost:3000/airports
Diese Seite ist für den Endbenutzer bestimmt. Es werden z. B. alle Flughäfen gelistet. Jedoch soll das Editieren und Löschen der Datensätze nicht möglich sein.
Um dies zu erreichen, sind folgende Schritte erforderlich:
- Generieren der Model-Klasse und dem Admin-Controller
Dem Namen des Models muss admin/ vorangestellt werden. Der Airports- Controller wird dann im Verzeichnis app/controllers/admin abgelegt.
ruby script/generate scaffold admin/ airport code:string \ name:string
Die Model-Klasse Airport wird jedoch wie gewohnt im Verzeichnis app/models abgelegt. Mit rake db:migrate führen Sie die Migration-Datei aus, um die Tabelle airports in der Datenbank zu erstellen.
- Generieren des Controllers für den Endbenutzer
Für den Endbenutzer kann man nun einen weiteren Controller anlegen, der z. B. nur die Flüge auflisten (Action index) und zu einem Flug die Details anzeigen kann (Action show).
Hierfür verwenden wir nicht den scaffold -Generator, sondern lediglich den controller -Generator.
ruby script/generate controller airports index show
Plural verwenden
Beachten Sie, dass Sie unbedingt die Pluralform von »Airport« verwenden. Der controller -Generator erstellt für uns leere Actions und leere Templates, die noch ausprogrammiert bzw. angelegt werden müssen. Wir könnten im Controller z. B. Folgendes eintragen:
Listing app/controllers/airport
class AirportsController < ApplicationController def index @airports = Airport.find(:all) end def show @airport = Airport.find(params[:id]) end end
Zusätzlich müssen noch die Template-Dateien mit Inhalten gefüllt werden.
- Routing-Eintrag
In der Routing-Datei config/routes.rb benötigen wir einen Routing-Eintrag für den Endbenutzer-Controller und einen für den Admin-Controller:
map.resources :airports map.namespace(:admin) do |admin| admin.resources :airports end
Zur Verlinkung der Seiten kann im View dann z. B. folgender Aufruf eingesetzt werden:
<%= link_to "Airport-Liste", airports_path %> <%= link_to "Airport-Administration", admin_airports_path %>
Singleton-Ressourcen
Anwendung
Auf vielen Websites gibt es die Möglichkeit, dass ein Benutzer seinen Account, in dem u. a. die Adressinformationen gespeichert werden, online pflegen kann. Dem Benutzer soll es möglich sein, einen Account anzulegen, zu bearbeiten und gegebenenfalls wieder zu entfernen. Der Benutzer soll jedoch keinen Zugriff auf die anderen Accounts haben. Das heißt, er soll immer nur Zugriff auf einen Account haben. Dieser Sachverhalt soll sich auch in der URL widerspiegeln.
Aufruf
Mit der folgenden URL könnte ein Benutzer auf seinen Account zugreifen:
http://localhost:3000/account
Wichtig ist hier, dass account im Singular angegeben wird. Es kann weder auf alle noch auf einen bestimmten Account mit einer angegebenen ID zugegriffen werden. Bei einer Singleton-Ressource wird die Ressource nur im Singular angegeben.
Im Folgenden wird gezeigt, wie eine Singleton-Ressource angelegt wird.
- Model anlegen
Für die Speicherung der Account-Daten benötigen wir ein Model und ein Migration-Skript zum Anlegen einer Datenbanktabelle:
ruby script/generate model account firstname:string \ lastname:string email:string ...
- Erstellen eines Controllers
Zur Realisierung kann ein Controller mit dem controller -Generator angelegt werden. Wichtig ist, dass der Name des Controllers im Singular angegeben wird. Als weitere Optionen übergeben wir die Actions, die generiert werden sollen.
ruby script/generate controller account show new edit \ create update destroy
Die Actions sind leer und müssen daher selbst ausprogrammiert werden. Damit das System weiß, welchen Account es laden soll, muss ein Authentifizierungssystem implementiert werden.
Der scaffold - oder resource -Generator ist leider nicht so geeignet, da dieser nur Plural-Ressourcen anlegt.
- Routing-Eintrag
In der Routing-Datei config/routes.rb legen wir folgenden Eintrag an. Achten Sie darauf, dass auch hier die Singular-Schreibweise verwendet wird:
map.resource :account
Aufgrund dieses Routing-Eintrages sind folgende Zugriffe auf die Ressource account möglich:
- GET /account
Es wird der Account-Controller mit der Action show aufgerufen. Diese Action zeigt die Details eines Benutzers. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Account-Details', account_path
- GET /account/new
Es wird der Account-Controller mit der Action new aufgerufen. Diese Action lädt das Formular zum Anlegen eines neuen Accounts. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'neuer Account', new_account_path
- GET /account/edit
Es wird der Account-Controller mit der Action edit aufgerufen. Diese Action lädt das Formular zum Bearbeiten eines Accounts. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Account bearbeiten', edit_account_path
- POST /account
Es wird der Account-Controller mit der Action create aufgerufen. Diese Action erstellt einen neuen Account-Datensatz. Der Aufruf erfolgt über ein Formular:Die HTTP-Methode muss nicht angegeben werden, da sie automatisch erkannt wird. Wenn das Objekt @account neu ist, wird die POST-Methode verwendet.form_for(@account) do ... end.
- PUT /account
Es wird der Account-Controller mit der Action update aufgerufen. Diese Action ändert die Daten des Account-Datensatzes. Der Aufruf erfolgt über ein Formular:Die HTTP-Methode muss nicht angegeben werden, da sie automatisch erkannt wird. Wenn das Objekt @account bereits in der Datenbank vorliegt, wird automatisch die PUT-Methode ausgeführt.form_for(@account) do ... end
- DELETE /account
Es wird der Account-Controller mit der Action destroy aufgerufen. Diese Action löscht den Account. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to "löschen", account_path, :method => :delete
Admin-Controller
Für Administratoren würde es sich anbieten, einen weiteren Controller anzulegen, der für die Verwaltung aller Accounts zuständig ist (siehe Abschnitt kap_resful_namespace). Folgendes Routing würde sich dann anbieten:
Listing config/routes.rb
map.resource :account map.namespace(:admin) do |admin| admin.resources :account end
Ressourcen erweitern
Mehr Actions erforderlich
Wenn wir den scaffold -Generator einsetzen, stehen uns sieben Actions im Controller zur Verfügung, um eine Ressource zu verarbeiten. Dies reicht gelegentlich jedoch nicht aus.
Beispiel
Angenommen, wir haben eine Bookmark-Ressource. Neben der Verwaltung (Anzeigen, Ändern, Löschen usw.) soll es möglich sein, Bookmarks als wichtig zu markieren oder die Markierung zu entfernen. Außerdem soll es möglich sein, alle markierten Bookmarks aufzulisten.
Dazu sind drei Schritte erforderlich:
- Model um Attribut »flag« ergänzen
Falls Sie bereits eine Tabelle bookmarks haben, diese jedoch noch nicht das Feld flag aufweist, können Sie es mit folgender Migration hinzufügen:
ruby script/generate migration AddFlagToBookmarks \ flag:boolean rake db:migrate
- Controller um die fehlenden Actions ergänzen
Fügen Sie im BookmarksController die folgenden Actions hinzu:
class BookmarksController << ApplicationController ... def flag @bookmark = Bookmark.find(params[:id]) @bookmark.update_attribute(:flag, true) render :show end def unflag @bookmark = Bookmark.find(params[:id]) @bookmark.update_attribute(:flag, false) render :show end def flagged @bookmarks = Bookmark.find(:all, :conditions => {:flag=>true}) render :index end end
- Routing-Eintrag
Damit die entsprechenden Actions über die URL aufgerufen werden können, muss noch folgender Routing-Eintrag vorgenommen werden:
map.resources :bookmarks, :member => { :flag => :put, :unflag => :put }, :collection => {:flagged => :get }
Da die Actions flag und unflag sich auf ein konkretes Element beziehen, werden sie innerhalb der Option :member definiert. Außerdem wird die HTTP-Methode angegeben.
Da sich die Action flagged nicht auf ein bestimmtes Element bezieht, wird sie innerhalb der Option :collection definiert. Auch hier wird die HTTP-Methode angegeben.
Aufruf
Aufgrund dieses Routing-Eintrages kann die erweiterte Ressource wie folgt aufgerufen werden:
- PUT http://localhost:3000/bookmarks/2/flag
Es wird die Action flag aus dem Bookmarks-Controller aufgerufen. Diese Action setzt das Attribut flag mit der ID 2 auf den Wert true . Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Markieren', flag_bookmark_path(2), :method=>:put
- PUT http://localhost:3000/bookmarks/2/unflag
Es wird die Action unflag aus dem Bookmarks-Controller aufgerufen. Diese Action setzt das Attribut flag des Bookmarks mit der ID 2 auf den Wert false . Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Markierung aufheben', unflag_bookmark_path(2), :method=>:put
- GET http://localhost:30000/bookmarks/flagged
Es wird die Action flagged aus dem Bookmarks-Controller aufgerufen. Diese Action listet alle Bookmarks, deren Attribut flag auf true gesetzt ist. Der Aufruf kann mit folgendem View-Helper erfolgen:link_to 'Alle markierten Bookmarks listen', flagged_bookmarks_path
Routing-Regeln anzeigen Rails bietet einen sehr praktischen Rake-Task an, der alle Routing-Regeln ausgibt:
rake routes
airports GET /airports {:controller=>"... }
airport GET /airports/:id {:controller=>"... }
new_airport GET /airports/new {:controller=>"... }
edit_airport GET /airports/:id/edit {:controller=>"... }
...
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.
- GET /airports/1