Damit stehen uns folgende Routen zur Verfügung:
MacBook:shop xyz$ rake routes
home_index GET /home/index(.:format) home#index
home_ping GET /home/ping(.:format) home#ping
home_pong GET /home/pong(.:format) home#pong
root / home#index
home_apfelmus /home/apfelmus(.:format) home#ping
MacBook:shop xyz$
Wenn Sie der Route
/home/apfelmus
einen anderen
Namen geben wollen, können Sie dies mit der Einstellung
:as =>
"name
"
in der
config/routes.rb
realisieren:
Shop::Application.routes.draw do
get "home/index"
get "home/ping"
get "home/pong"
root :to => "home#index"
match "home/ping-ping" => "home#ping", :as => "apfelmus"
end
Jetzt stehen Ihnen im System folgende Routen-Namen zur
Verfügung:
MacBook:shop xyz$ rake routes
home_index GET /home/index(.:format) home#index
home_ping GET /home/ping(.:format) home#ping
home_pong GET /home/pong(.:format) home#pong
root / home#index
apfelmus /home/ping-ping(.:format) home#ping
MacBook:shop xyz$
Sie sehen dabei, dass eine URL ein Minuszeichen enthalten darf, aber
der Routen-Name nicht (ein Unterstrich geht aber).
Match kann nicht nur fixe Routen zuordnen, sondern auch noch
Parameter übergeben. Ein typisches Beispiel dafür wären Angaben zu einem
Datum. Wir erstellen dazu nachfolgend eine
Mini-Blog-Applikation:
MacBook:~ xyz$ rails new blog
[...]
MacBook:~ xyz$ cd blog
MacBook:blog xyz$ rails generate scaffold Post subject content published_at:date
[...]
MacBook:blog xyz$ rake db:migrate
[...]
MacBook:blog xyz$
Als Beispieldaten in der
db/seeds.rb
nehmen
wir:
Post.create(:subject => 'Ein Test', :published_at => '01.10.2011')
Post.create(:subject => 'Noch ein Test', :published_at => '01.10.2011')
Post.create(:subject => 'Und wieder ein Test', :published_at => '02.10.2011')
Post.create(:subject => 'Letzter Test', :published_at => '01.11.2011')
Post.create(:subject => 'Allerletzter Test', :published_at => '01.11.2012')
Mit
rake db:seed spielen wir diese Daten in die
Datenbank:
MacBook:blog xyz$ rake db:seed
MacBook:blog xyz$
Wenn wir jetzt mit
rails server den
Rails-Server starten und mit dem Browser auf die Seite
http://0.0.0.0:3000/posts
surfen, bekommen wir folgende Anzeige:
Für ein solches Blog wäre es natürlich sehr praktisch, wenn man
mit der URL
http://0.0.0.0:3000/2010/
alle Einträge für das Jahr 2010 und mit
http://0.0.0.0:3000/2010/10/01
alle Einträge für den 01.10.2010 anzeigen könnte. Das erreichen wir mit
optionalen Parametern beim
match
-Eintrag. Bitte tragen Sie
folgende Konfiguration in die
config/routes.rb
ein:
Blog::Application.routes.draw do
resources :posts
match "/:year(/:month(/:day))" => "posts#index"
end
Die runden Klammern stehen für optionale Parameter. In diesem Fall
muss unbedingt das Jahr, aber nicht zwingend der Monat oder der Tag
angegeben werden.
Wenn wir nichts anderes ändern, bekommen wir beim Aufruf von
http://0.0.0.0:3000/2010/
und
http://0.0.0.0:3000/2010/10/01
immer noch das gleiche Ergebnis wie beim Aufruf von
http://0.0.0.0:3000/posts
.
Das ist ja auch logisch. Aber schauen Sie einmal in die Ausgabe von
rails server:
Started GET "/2010/" for 127.0.0.1 at 2012-05-22 11:27:29 +0200
Processing by PostsController#index as HTML
Parameters: {"year"=>"2010"}
Post Load (0.2ms) SELECT "posts".* FROM "posts"
Rendered posts/index.html.erb within layouts/application (5.5ms)
Completed 200 OK in 13ms (Views: 11.3ms | ActiveRecord: 0.2ms)
Die Route wurde erkannt und dem Hash
params
(in der Ausgabe irreführend als
Parameters
ausgeschrieben) ein Element
"year" =>
"2010"
zugewiesen. Ein Aufruf der URL
http://0.0.0.0:3000/2010/12/24
ergibt erwartungsgemäß folgende Ausgabe:
Started GET "/2010/12/24" for 127.0.0.1 at 2012-05-22 11:30:49 +0200
Processing by PostsController#index as HTML
Parameters: {"year"=>"2010", "month"=>"12", "day"=>"24"}
Post Load (0.2ms) SELECT "posts".* FROM "posts"
Rendered posts/index.html.erb within layouts/application (5.9ms)
Completed 200 OK in 13ms (Views: 11.5ms | ActiveRecord: 0.2ms)
Auf
params[]
haben wir im Controller Zugriff
auf die in der URL definierten Werte. Wir müssen nur noch die
index
Methode in
app/controllers/posts_controller.rb
anpassen, um
die für das entsprechende Datum, den entsprechenden Monat oder das
entsprechende Jahr eingetragenen
posts
auszugeben:
def index
if params[:day]
@posts = Post.where(:published_at => Date.parse("#{params[:day]}.#{params[:month]}.#{params[:year]}"))
elsif params[:month]
@posts = Post.where(:published_at => ( Date.parse("01.#{params[:month]}.#{params[:year]}") .. Date.parse("01.#{params[:month]}.#{params[:year]}").end_of_month ))
elsif params[:year]
@posts = Post.where(:published_at => ( Date.parse("01.01.#{params[:year]}") .. Date.parse("31.12.#{params[:year]}") ))
else
@posts = Post.all
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: @posts }
end
end
Constraints
(Einschränkungen)
In
„Parameter“ habe ich Ihnen
gezeigt, wie man Parameter aus der URL auslesen und an den Controller
weitergeben kann. Leider hat der dort definierte Eintrag in der
config/routes.rb
match "/:year(/:month(/:day))" => "posts#index"
einen
erheblichen Nachteil: Er überprüft die einzelnen Elemente nicht. Die URL
http://0.0.0.0:3000/ein/beispiel/dafuer
wird genauso gematcht und führt dann natürlich direkt zu einem
Fehler:
In der Log-Ausgabe in
log/development.log
sehen wir dabei folgenden Eintrag:
Started GET "/ein/beispiel/dafuer" for 127.0.0.1 at 2012-05-22 13:20:44 +0200
Processing by PostsController#index as HTML
Parameters: {"year"=>"ein", "month"=>"beispiel", "day"=>"dafuer"}
Es ist klar, dass Date.parse( "dafuer.beispiel.ein")
nicht funktionieren kann. Ein Datum besteht nun mal aus Zahlen und nicht
aus Buchstaben.
Constraints können mittels Regular Expressions den Inhalt der URL
besser definieren. In unserem Blog-Fall würde die
config/routes.rb
mit Constraints so
aussehen:
Blog::Application.routes.draw do
resources :posts
match "/:year(/:month(/:day))" => "posts#index", :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }
end
Warnung
Bitte beachten Sie, dass bei den Regular Expressions in einem
Constraint keine Regex-Anchors wie "^" benutzt werden können.
Wenn wir mit dieser Konfiguration noch mal die URL aufrufen,
bekommen wir einen Fehler „No route matches“ von Rails:
Mit der Route
match "/:year(/:month(/:day))" =>
"posts#index", :constraints => { :year => /\d{4}/, :month =>
/\d{2}/, :day => /\d{2}/ }
haben wir zwar die URL
syntaktisch auf ein Datum überprüft, aber es kann natürlich ein User
immer noch die URL
http://0.0.0.0:3000/2011/02/31
aufrufen. Da es keinen 31. Februar gibt, dürfte es diese Route logisch
nicht geben. Wir brauchen also eine Möglichkeit, die ein angegebenes
syntaktisch komplettes Datum darauf überprüft, ob es auch ein
korrektes Datum nach dem Kalender ist.
Dazu müssen wir eine eigene Klasse definieren, mit der Objekte
mit der Methode
matches?()
definiert werden.
Innerhalb von
matches?()
können wir die
gewünschte eigene Validierung vornehmen, um dann mit
true
oder
false
zu antworten. Bitte erstellen Sie dazu die
Datei
lib/valid_date_contraint.rb
mit folgendem
Inhalt:
class ValidDateConstraint
def matches?(request)
begin
Date.parse("#{request.params[:day]}.#{request.params[:month]}.#{request.params[:year]}")
true
rescue
false
end
end
end
Diese Klasse müssen wir jetzt laden. Bitte erstellen Sie dazu
die Datei
config/initializers/load_extensions.rb
mit diesem Inhalt:
require 'valid_date_constraint'
Jetzt splitten wir die Datums-Route in der
config/routes.rb
auf:
Blog::Application.routes.draw do
resources :posts
match "/:year/:month/:day" => "posts#index", :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }, :constraints => ValidDateConstraint.new
match "/:year(/:month)" => "posts#index", :constraints => { :year => /\d{4}/, :month => /\d{2}/ }
end
Das erste
match
reagiert auf alle URLs
mit drei Parametern, die syntaktisch den gegebenen Contraints
entsprechen. Zusätzlich wird noch das Datum überprüft. Erst wenn das
Datum an sich valide ist, wird diese Route benutzt. Sie wird also
nicht bei der URL
http://0.0.0.0:3000/2011/02/31
aufgerufen. Hingegen würde die URL
http://0.0.0.0:3000/2011/10/01
funktionieren.
Ich kann mit einem
match
auch auf eine andere Seite
umleiten (
redirect). Wenn
ich die Eingabe der unsinnigen URL
http://0.0.0.0:3000/2010/02/31
auf /2010/02 redirecten will, dann geht das
folgendermaßen:
match "/:year/02/31" => redirect("/%{year}/02")
Damit könnte man auch die Eingabe eines einstelligen Monats auf
einen zweistelligen Monat umleiten:
match "/:year/:month/:day" => redirect("/%{year}/0%{month}/%{day}"), :constraints => { :year => /\d{4}/, :month => /\d{1}/, :day => /\d{2}/ }
Das Gleiche ginge natürlich auch für einen einstelligen Tag. Unter
Berücksichtigung aller Kombinationen sähe unsere
config/routes.rb
dann so aus:
Blog::Application.routes.draw do
resources :posts
match "/:year/:month/:day" => redirect("/%{year}/0%{month}/%{day}" ), :constraints => { :year => /\d{4}/, :month => /\d{1}/, :day => /\d{2}/ }
match "/:year/:month/:day" => redirect("/%{year}/0%{month}/0%{day}"), :constraints => { :year => /\d{4}/, :month => /\d{1}/, :day => /\d{1}/ }
match "/:year/:month/:day" => redirect("/%{year}/%{month}/0%{day}" ), :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{1}/ }
match "/:year/:month" => redirect("/%{year}/0%{month}"), :constraints => { :year => /\d{4}/, :month => /\d{1}/ }
match "/:year(/:month(/:day))" => "posts#index", :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }
end
Mit diesem Redirect-Regelwerk wäre sichergestellt, dass ein
Benutzer der Seite auch einstellige Monate und Tage eingeben kann und
trotzdem an der richtigen Stelle landet bzw. zum richtigen Format
umgeleitet wird.
Anmerkung
Redirects in der config/routes.rb
sind per
Default Redirects mit dem Code 301 ("Moved Permanetly").