Więcej na rubyonrails.pl: Start | Pobierz | Wdrożenie | Kod (en) | Screencasty | Dokumentacja | Ekosystem | Forum | IRC

Routing w Railsach

Poniższy tekst to przewodnik po opcjach związanych z routingiem (routing) w frameworku Rails, z którymi spotykają użytkownicy. W oparciu o niego, będziesz mógł:

1 Podwójny cel routingu

Routing w Railsach jest to dwukierunkowy mechanizm – tak, jakby móc przerobić drzewa na papier, a potem z papieru z powrotem utworzyć drzewa. Dokładnie rzecz biorąc, drogi routingu łączą nadchodzące żądania HTTP z kodem w kontrolerach Twojej aplikacji i pomagają generować adresy URL bez konieczności ręcznego zapisania ich jako łańcuchów znaków.

1.1 Łączenie adresu URL z kodem

Silnik routingu wewnątrz Railsów jest to fragment kodu, który przekazuje żądanie HTTP do odpowiedniego fragmentu aplikacji. Gdy Twoja aplikacja otrzymuje żądanie, np.

GET /patients/17

najprawdopodobniej wykona akcję show dla kontrolera patients, wyświetlając informacje o pacjencie posiadającym ID równe 17.

1.2 Generowanie adresu URL z kodu źródłowego

Routing działa także w drugą stronę. Załóżmy, że Twoja aplikacja zawiera kod:

@patient = Patient.find(17) <%= link_to "Patient Record", patient_path(@patient) %>

Mechanizm rutingu działa jako element tłumaczący ten kod na adres URL, np http://example.com/patients/17. Korzystając z routingu w ten sposób sprawisz, że Twoja aplikacja będzie bardziej niezawodna i łatwiejsza do zrozumienia niż w wypadku, gdybyś ręcznie wpisywał wszystkie linki URL.

Obiekt Patient musi być zadeklarowany jako zasób dostępny dla nazwanej drogi, by mógł zostać prawidłowo obsłużony przez ten kod.

2 Krótki przegląd routes.rb

Za routing w Railsach odpowiadają dwa moduły: silnik routingu, który jest integralną częścią frameworku oraz plik config/routes.rb, zawierający definicje dróg routingu używanych w aplikacji. Poznanie dokładnej zawartości pliku routes.rb jest podstawowym celem tego przewodnika. Zanim jednak zagłębimy się w tym temacie, proponuję krótki przegląd pliku.

2.1 Przetwarzanie pliku

Pod względem formatu, routes.rb jest po prostu dużym blokiem wysłanym do ActionController::Routing::Routes.draw. Wewnątrz tego bloku możesz umieszczać komentarze, jednak najprawdopodobniej większość jego zawartości to pojedyncze linie kodu – każda z nich odpowiada za drogę routingu w aplikacji. W pliku tym można znaleźć pięć podstawowych typów zawartości:

  • drogi RESTful
  • drogi nazwane
  • drogi zagnieżdżone
  • drogi regularne
  • drogi domyślne

Każdą z nich omówimy dokładniej w dalszej części tego przewodnika.

W momencie nadejścia żądania, plik routes.rb jest przetwarzany w kolejności “z góry na dół”. W momencie odnalezienia pierwszego pasującego wpisu, żądanie zostaje przekazane przez powiązaną z nim drogę do odpowiedniego kontrolera. W przypadku, gdy w pliku nie będzie żadnej pasującej do żądania drogi routingu, Railsy zwrócą status 404.

2.2 Drogi RESTful

Drogi RESTful korzystają z wbudowanej w Railsy orientacji REST, by umożliwić zapisanie jak najwięcej informacji o routingu w jednej deklaracji. Ścieżka RESTful wygląda tak:

map.resources :books

2.3 Drogi nazwane

Drogi nazwane dają bardzo czytelne linki w kodzie źródłowym. Poniżej przedstawiona została typowa droga nazwana:

map.login '/login', :controller => 'sessions', :action => 'new'

2.4 Drogi zagnieżdżone

Drogi zagnieżdżone umożliwiają deklarację ścieżki do zasobu zawartego wewnątrz innego zasobu. Później zobaczysz jak taka droga umożliwia tłumaczenie kodu na adresy i ścieżki dostępu. Na przykład, jeśli aplikacja składa się z kilku części, a każda z nich należy do pewnej grupy, możesz stosować zagnieżdżoną drogę routingu:

map.resources :assemblies do |assemblies| assemblies.resources :parts end

2.5 Drogi regularne

W wielu aplikacjach spotkasz się także z niezgodnym z założeniami REST routingiem, który wskazuje wprost powiązanie fragmentów URL z konkretną akcją. Przykład ścieżki regularnej to:

map.connect 'parts/:number', :controller => 'inventory', :action => 'show'

2.6 Drogi domyślne

Domyślne drogi routingu są siecią zabezpieczeń, obsługującą żądania które nie pasowały do żadnej innej drogi. Wiele aplikacji Railsowych zawiera taką parę ścieżek domyślnych:

map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'

Te drogi routingu są automatycznie generowane podczas tworzenia nowej aplikacji Railsowej. Jeśli cała Twoja aplikacja korzysta z routingu RESTful, prawdopodobnie będziesz chciał je usunąć. Zanim jednak to zrobisz, upewnij się, że nie są one faktycznie nigdy używane!

3 Routing RESTful: domyślny routing Railsów

Routing RESTful jest obecnie standardowy w Railsach i to jego powinieneś preferować tworząc nowe aplikacje. Zrozumienie jak działa routing RESTful może wydawać się trochę trudne, jednak na pewno się opłaca; dzięki niemu Twój kod będzie dużo bardziej czytelny, a Twoja praca w końcu przestanie wyglądać jak walczenie z frameworkiem.

3.1 Czym jest REST?

Założenia routingu REST pochodzą z pracy doktorskiej Roya Fieldinga: Architectural Styles and the Design of Network-based Software Architectures. Na szczęście nie musisz czytać całego dokumentu, by zrozumieć ideę REST w Railsach. REST jest to skrót od angielskiej nazwy “Representational State Transfer”, czyli Reprezentacyjny Transfer Stanu. Sprowadza się on do dwóch głównych założeń:

  • używanie identyfikatorów zasobów (dla celów tego przewodnika możesz je utożsamiać z adresami URL) do ich reprezentacji
  • transferowanie reprezentacji stanów każdego zasobu pomiędzy elementami systemu.

Na przykład, żądanie takie jak:

DELETE /photos/17

będzie odczytane przez aplikację railsową jako odniesienie do zasobu będącego zdjęciem o ID równym 17, a akcją wymaganą przez takie żądanie będzie usunięcie wskazanego zasobu. REST jest naturalnym stylem architektury aplikacji sieciowej, a Railsy czynią go jeszcze bardziej przyjaznym dzięki konwencjom upraszczającym jego stosowanie.

3.2 CRUD, typy żądań i akcje

W Railsach, droga routingu RESTful łączy żądanie HTTP, akcję kontrolera i, pośrednio, operację CRUD na bazie danych. Pojedynczy wpis do pliku routingu, taki jak:

map.resources :photos

tworzy siedem różnych dróg routingu w aplikacji:

żądanie HTTP URL kontroler akcja cel użycia
GET /photos Photos index wyświetlenie listy wszystkich zdjęć
GET /photos/new Photos new zwrócenie formularza HTML do tworzenia zdjęcia
POST /photos Photos create tworzenie nowego zdjęcia
GET /photos/1 Photos show wyświetlenie konkretnego zdjęcia
GET /photos/1/edit Photos edit zwrócenie formularza HTML do edycji zdjęcia
PUT /photos/1 Photos update aktualizacja konkretnego zdjęcia
DELETE /photos/1 Photos destroy usunięcie konkretnego zdjęcia

Dla dróg routingu odnoszących się do jednego konkretnego zasobu, identyfikator zasobu będzie dostępny dla akcji odpowiedniego kontrolera jako parametr params[:id].

Jeśli stale używasz dróg RESTful, powinieneś wyłączyć drogi domyślne w pliku routes.rb, aby Railsy wymusiły routowanie oparte na typach żądań.

3.3 Adresy URL i ścieżki dostępu

Stworzenie drogi routingu RESTful powoduje także udostępnienie kilku helperów wewnątrz aplikacji:

  • photos_url i photos_path wskazują ścieżki dostępu do akcji index i create
  • new_photo_url i new_photo_path wskazują ścieżkę dostępu do akcji new
  • edit_photo_url i edit_photo_path wskazują ścieżkę dostępu do akcji edit
  • photo_url i photo_path wskazują ścieżki dostępu do akcji show, update i destroy

Ponieważ routing opiera się zarówno na typach żądań HTTP jak i na ścieżkach dostępu w nich zawartych, siedem dróg routingu wymaga określenia tylko czterech par helperów.

W każdym przypadku, helper _url generuje łańcuch znaków zawierający pełny adres URL jaki będzie zrozumiały dla aplikacji, a _path – łańcuch zawierający ścieżkę dostępu odnoszącą się do katalogu głównego aplikacji. Na przykład:

photos_url # => "http://www.example.com/photos" photos_path # => "/photos"

3.4 Definiowanie kilku zasobów jednocześnie

Jeśli chcesz utworzyć drogi routingu do więcej niż jednego zasobu RESTful, możesz zaoszczędzić dużo kodu definiując je wewnątrz jednego odwołania do map.resources:

map.resources :photos, :books, :videos

Taki zapis jest równoważny z poniższym:

map.resources :photos map.resources :books map.resources :videos

3.5 Pojedyncze zasoby

Możesz także zastosować routing RESTful do pojedynczego zasobu wewnątrz aplikacji. W tym przypadku zamiast map.resources, użyj map.resource, a generowane drogi routingu będą troszkę inne. Na przykład, wpis routingu:

map.resource :geocoder

stworzy sześć dróg routingu w aplikacji:

żądanie HTTP URL kontroler akcja cel użycia
GET /geocoder/new Geocoders new zwrócenie formularza HTML do tworzenia geokodera
POST /geocoder Geocoders create tworzenie nowego geokodera
GET /geocoder Geocoders show wyświetlenie jednego jedynego geokodera
GET /geocoder/edit Geocoders edit zwrócenie formularza HTML do edycji geokodera
PUT /geocoder Geocoders update akrualizacja geokodera
DELETE /geocoder Geocoders destroy usunięcie geokodera

Pomimo, ze nazwa zasobu jest w liczbie pojedynczej, (Geocoder), odpowiadający za nią kontroler ma nazwę w liczbie mnogiej (Geocoders).

Pojedyncza ścieżka routingu RESTful generuje zmniejszony zestaw helperów:

  • new_geocoder_url i new_geocoder_path wskazują ścieżkę dostępu do akcji new
  • edit_geocoder_url i edit_geocoder_path wskazują ścieżkę dostępu do akcji edit
  • geocoder_url i geocoder_path wskazują ścieżki dostępu do akcji create, show, update i destroy

3.6 Dopasowanie zasobów

Chociaż konwencje routingu RESTful są wystarczające dla większości aplikacji, można jednak dopasować do własnych potrzeb wiele aspektów jego działania. Służą do tego następujące opcje:

  • :controller
  • :singular
  • :requirements
  • :conditions
  • :as
  • :path_names
  • :path_prefix
  • :name_prefix
  • :only
  • :except

Możesz także tworzyć dodatkowe drogi routingu przy pomocy opcji :member i :collection, które zostaną omówione w dalszej części tego przewodnika.

3.6.1 Użycie :controller

Opcja :controller pozwala użyć kontrolera o innej nazwie niż dostępna od zewnątrz nazwa zasobu. Na przykład, wpis:

map.resources :photos, :controller => "images"

rozpozna przychodzące żądania zawierające photo, lecz skieruje je do kontrolera Images:

żądanie HTTP URL kontroler akcja cel użycia
GET /photos Images index wyświetlenie listy wszystkich zdjęć
GET /photos/new Images new zwrócenie formularza HTML do tworzenia zdjęcia
POST /photos Images create tworzenie nowego zdjęcia
GET /photos/1 Images show wyświetlenie konkretnego zdjęcia
GET /photos/1/edit Images edit zwrócenie formularza HTML do edycji zdjęcia
PUT /photos/1 Images update aktualizacja konkretnego zdjęcia
DELETE /photos/1 Images destroy usunięcie konkretnego zdjęcia

Wygenerowane helpery będą zawierały nazwę zasobu, a nie kontrolera. W powyższym przykładzie nadal otrzymamy helpery takie jak photos_path, new_photo_path itd.

3.7 Routing a przestrzenie nazw kontrolerów

Railsy pozwalają na grupowanie kontrolerów w przestrzenie nazw (namespaces) poprzez umieszczenie ich w podfolderach app/controllers. Opcja :controller umożliwia wygodny routing do takich kontrolerów. Na przykład, możesz posiadać zasób, którego kontroler służy jedynie do celów administracyjnych, umieszczony w podkatalogu admin:

map.resources :adminphotos, :controller => "admin/photos"

Jeśli grupujesz w ten sposób kontrolery, zwróc uwagę na pewien niuans routingu: Railsy zawsze starają się znaleźć zasób najbardziej zbliżony do tego, którego użyły w poprzednim żądaniu. Na przykład, jeśli oglądasz obecnie widok wygenerowany przez helper adminphoto_path i klikniesz w link wygenerowany kodem <%= link_to "show", adminphoto(1) %>, zostaniesz przekierowany do widoku wygenerowanego przez admin/photos/show, ale w to samo miejsce przekieruje Cię także link <%= link_to "show", {:controller => "photos", :action => "show"} %>, ponieważ Railsy wygenerują URL nawiązujący do obecnego adresu URL.

Aby zapewnić, że link wskazuje na kontroler najwyższego poziomu, poprzedź nazwę kontrolera ukośnikiem: <%= link_to "show", {:controller => "/photos", :action => "show"} %>

Możesz także określić przestrzeń nazw kontrolera używając opcji :namespace zamiast podawać ścieżkę dostępu:

map.resources :adminphotos, :namespace => "admin", :controller => "photos"

Może to być szczególnie przydatne, jeśli połączymy to zastosowanie z with_options w celu połączenia kilku dróg routingu naraz:

map.with_options(:namespace => "admin") do |admin| admin.resources :photos, :videos end

Taki zapis zapewni routing dla kontrolerów admin/photos i admin/videos.

3.7.1 Użycie :singular

Jeśli z jakiegoś powodu Railsy nie radzą sobie prawidłowo z utworzeniem nazwy pojedynczej pochodzącej od nazwy w liczbie mnogiej, możesz wymusić zmianę tej nazwy korzystając z opcji :singular:

map.resources :teeth, :singular => "tooth"

W zależności od reszty aplikacji, możesz zamiast tego stworzyć dodatkowe reguły dla klasy Inflector.

3.7.2 Użycie :requirements

Możesz użyć opcji :requirements w drodze routingu RESTful, by wymusić odpowiednie formatowanie parametru :id w pojedynczych ścieżkach routingu. Na przykład:

map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/}

Ta deklaracja spowoduje, że parametr :id będzie musiał być zgodny z podanym wyrażeniem regularnym. W tym przypadku, żądanie /photos/1 nie będzie dłużej pasowało do tej drogi routingu, ale np. photos/RR27 będzie.

3.7.3 Użycie :conditions

Ten parametr jest obecnie używany tylko dla ustawienia typu żądań HTTP dotyczących indywidualnych ścieżek routingu. W teorii możesz ustawić go dla ścieżek RESTful, ale w praktyce nie ma to większego sensu. (Więcej o warunku :conditions dowiesz się z rozdziału dotyczącego klasycznego routingu w tym przewodniku.)

3.7.4 Użycie :as

Opcja :as pozwala nadpisać domyślną nazwę dla tworzonych ścieżek dostępu. Przykładowo, wpis:

map.resources :photos, :as => "images"

rozpozna nadchodzące żądanie zawierające w URL słowo image, ale ścieżka routingu skieruje je do kontrolera Photos:

żądanie HTTP URL kontroler akcja cel użycia
GET /images Photos index wyświetlenie listy wszystkich zdjęć
GET /images/new Photos new zwrócenie formularza HTML do tworzenia zdjęcia
POST /images Photos create tworzenie nowego zdjęcia
GET /images/1 Photos show wyświetlenie konkretnego zdjęcia
GET /images/1/edit Photos edit zwrócenie formularza HTML do edycji zdjęcia
PUT /images/1 Photos update aktualizacja konkretnego zdjęcia
DELETE /images/1 Photos destroy usunięcie konkretnego zdjęcia

Wygenerowane helpery będą zawierały nazwę zasobu, a nie ścieżki dostępu. W powyższym przykładzie nadal otrzymamy helpery takie jak photos_path, new_photo_path itd.

3.7.5 Użycie :path_names

Opcja :path_names pozwala nadpisać domyślne elementy new i edit w adresie URL:

map.resources :photos, :path_names => { :new => 'make', :edit => 'change' }

Taki zapis spowoduje, że routing Railsów rozpozna URL takie jak:

/photos/make /photos/1/change

Rzeczywista nazwa akcji nie ulegnie zmianie; drogi routingu nadal będą prowadziły do akcji new i edit.

Jeśli zechcesz zmienić tą opcją wszystkie drogi, możesz ustawić ją jako domyślną dla środowiska:

config.action_controller.resources_path_names = { :new => 'make', :edit => 'change' }
3.7.6 Użycie :path_prefix

Opcja :path_prefix pozwala umieścić dodatkowe parametry, by były zawarte w prefiksie pasującej ścieżki dostępu. Załóżmy na przykład, że każde zdjęcie zawarte w aplikacji jest przypisane do konkretnego autora. W tej sytuacji, możesz zadeklarować następującą ścieżkę routingu:

map.resources :photos, :path_prefix => '/photographers/:photographer_id'

Do ścieżek pasujących do tego wpisu będą należały między innymi:

/photographers/1/photos/2 /photographers/1/photos

W większości przypadków, łatwiej jest rozpoznawać takie URL poprzez stworzenie zagnieżdżonych zasobów, jak zostało to opisane w następnym rozdziale.

Możesz też użyć opcji :path_prefix dla dróg routingu innych niż RESTful.

3.7.7 Użycie :name_prefix

Możesz użyć opcji name_prefix by zapobiec kolizji wpisów. Funkcja staje się bardzo użyteczna, gdy masz dwa zasoby o tej samej nazwie, które dzięki użyciu :path_prefix będą routowane inaczej. Na przykład:

map.resources :photos, :path_prefix => '/photographers/:photographer_id', :name_prefix => 'photographer_' map.resources :photos, :path_prefix => '/agencies/:agency_id', :name_prefix => 'agency_'

Taka kombinacja utworzy helpery takie jak photographer_photos_path i agency_edit_photo_path.

Możesz też użyć opcji :name_prefix dla dróg routingu innych niż RESTful.

3.7.8 Użycie :only i :except

Railsy tworzą drogi routingu dla wszystkich siedmiu domyślnych akcji (index, show, new, create, edit, update i destroy) dla każdej drogi RESTful. Możesz użyć opcji :only i :except, by dopasować to zachowanie. Opcja :only wymusza wykonanie tylko wskazanych ścieżek routingu:

map.resources :photos, :only => [:index, :show]

Przy takim wpisie, żądanie GET odnoszące się do zasobu /photos zostanie obsłużone, ale żądanie POST dla /photos (normalnie wskazujące na akcję create) już nie.

Opcja :except jest odwrotnością :only – wskazuje ona na akcje, które nie zostaną obsłużone przez wpis:

map.resources :photos, :except => :destroy

W tym przypadku, zostaną utworzone wszystkie normalne drogi routingu oprócz tej obsługującej akcję destroy (żądanie DELETE odnoszące się do zasobu photos/_id_).

Poza nazwami akcji, możesz także użyć dla opcji :only i :except specjalnych symboli, takich jak: :all i :none.

Jeśli w aplikacji występuje dużo dróg RESTful, użycie opcji :only i :except, by wygenerować tylko potrzebne ścieżki, może znacząco obniżyć zużycie pamięci i przyspieszyć proces routingu.

3.8 Zasoby zagnieżdżone

Częstym przypadkiem są zasoby logicznie przypisane do innych zasobów. Załóżmy na przykład, że aplikacja zawiera modele:

class Magazine < ActiveRecord::Base has_many :ads end class Ad < ActiveRecord::Base belongs_to :magazine end

Każdy obiekt ad logicznie służy do obsługi jednego obiektu magazine. Drogi zagnieżdżone pozwalają zachować tę zależność podczas routingu. W tym przypadku, możesz użyć następującej deklaracji routingu:

map.resources :magazines do |magazine| magazine.resources :ads end

Poza drogami routingu do obiektów magazine, ta deklaracja stworzy także drogi do obiektów ad. Każda z nich będzie wymagała określenia w URL do którego obiektu magazine się odnosi:

żądanie HTTP URL kontroler akcja cel użycia
GET /magazines/1/ads Ads index wyświetlenie listy wszystkich ad dla wskazanego obiektu magazine
GET /magazines/1/ads/new Ads new zwrócenie formularza HTML do tworzenia obiektu ad należącego do wskazanego obiektu magazine
POST /magazines/1/ads Ads create tworzenie nowego obiektu ad należącego do wskazanego obiektu magazine
GET /magazines/1/ads/1 Ads show wyświetlenie konkretnego obiektu ad należącego do wskazanego obiektu magazine
GET /magazines/1/ads/1/edit Ads edit zwrócenie formularza HTML do edycji obiektu ad należącego do wskazanego obiekty magazine
PUT /magazines/1/ads/1 Ads update aktualizacja konkretnego obiektu ad należącego do wskazanego obiektu magazine
DELETE /magazines/1/ads/1 Ads destroy usunięcie konkretnego obiektu ad należącego do wskazanego obiektu magazine

Wpis ten stworzy także helpery, takie jak magazine_ads_url i edit_magazine_ad_path.

3.8.1 Użycie :name_prefix

Opcja :name_prefix nadpisuje automatycznie generowany prefiks w helperach dla zagnieżdżonych ścieżek routingu, na przykład:

map.resources :magazines do |magazine| magazine.resources :ads, :name_prefix => 'periodical' end

Stworzy to helpery takie jak periodical_ads_url i periodical_edit_ad_path. Możesz nawet użyć :name_prefix by całkowicie usunąć prefiksy:

map.resources :magazines do |magazine| magazine.resources :ads, :name_prefix => nil end

Spowoduje to utworzenie helperów ads_url i edit_ad_path. Zwróć uwagę na fakt, że dodanie identyfikatora nadal będzie wymagane przy użyciu helperów:

ads_url(@magazine) edit_ad_path(@magazine, @ad)
3.8.2 Użycie :has_one i :has_many

Opcje :has_one i :has_many umożliwiają zwięzłą notację prostych dróg zagnieżdżonych. Używamy :has_one dla zagnieżdżenia pojedynczego zasobu lub :has_many dla wielu zasobów:

map.resources :photos, :has_one => :photographer, :has_many => [:publications, :versions]

Spowoduje to ten sam efekt co wpisanie całego zestawu deklaracji:

map.resources :photos do |photo| photo.resource :photographer photo.resources :publications photo.resources :versions end
3.8.3 Ograniczenia zagnieżdżenia

Możesz zagnieździć zasoby wewnątrz innych zagnieżdżonych zasobów. Na przykład:

map.resources :publishers do |publisher| publisher.resources :magazines do |magazine| magazine.resources :photos end end

Jeśli jednak nie użyjesz opcji name_prefix => nil, wielokrotnie zagnieżdżone zasoby staną się bardzo niewygodne w użyciu. Dla tego przykładu, aplikacja poprawnie obsługiwałaby URL takie jak:

/publishers/1/magazines/2/photos/3

Odpowiedni helper miałby postać publisher_magazine_photo_url, co wymagałoby określania trzech poziomów obiektów. W praktyce jest to na tyle niewygodne i mylące rozwiązanie, że popularny artykuł autorstwa Jamisa Bucka wprowadza regułę szeroko stosowaną przy projektowaniu z wykorzystaniem frameworku Rails:

Zasoby nie powinny być nigdy zagnieżdżone na więcej niż jednym poziomie.

3.8.4 Płytkie zagnieżdżenie

Opcja :shallow oferuje eleganckie rozwiązanie problemów z głęboko zagnieżdżonymi drogami routingu. Jeśli użyjesz jej na dowolnym poziomie routingu, ścieżki dostępu do zagnieżdżonych zasobów odnoszące się do konkretnego elementu (tzn. takiego, który posiada parametr :id) nie będą posiadały obiektu nadrzędnego w prefiksie. Aby lepiej to wyjaśnić posłużę się przykładem:

map.resources :publishers, :shallow => true do |publisher| publisher.resources :magazines do |magazine| magazine.resources :photos end end

Ten wpis umożliwi rozpoznanie m.in. takich dróg routingu:

/publishers/1 ==> publisher_path(1) /publishers/1/magazines ==> publisher_magazines_path(1) /magazines/2 ==> magazine_path(2) /magazines/2/photos ==> magazines_photos_path(2) /photos/3 ==> photo_path(3)

Przy płytkim zagnieżdżeniu, wystarczy podać minimalną liczbę informacji potrzebną do jednoznaczej identyfikacji zasobu, by móc na nim pracować. Jeśli chcesz, możesz łączyć płytkie zagnieżdżenie z opcjami :has_one i :has_many:

map.resources :publishers, :has_many => { :magazines => :photos }, :shallow => true

3.9 Generowanie drogi routingu z tablic

Oprócz helperów routingu, Railsy mogą też generować drogi RESTful z tablicy parametrów. Załóżmy na przykład, że masz zestaw dróg routingu wygenerowanych na podstawie następujących wpisów w pliku routes.rb:

map.resources :magazines do |magazine| magazine.resources :ads end

Railsy wygenerują helpery takie jak magazine_ad_path, które mogą służyć do tworzenia linków:

<%= link_to "Ad details", magazine_ad_path(@magazine, @ad) %>

Inny sposobem odniesienia się do tej drogi routingu jest tablica obiektów:

<%= link_to "Ad details", [@magazine, @ad] %>

Ten format jest bardzo wygodny, gdy nie wiesz z góry jaki typów obiektu będzie użyty w danym linku.

3.10 Zasoby z przestrzenią nazw

Łącząc opcje :path_prefix i :name_prefix możesz osiągnąć dosyć skomplikowane struktury. Na przykład, możesz użyć takiej kombinacji by przenieść zasoby administracyjne do oddzielnego katalogu w aplikacji:

map.resources :photos, :path_prefix => 'admin', :controller => 'admin/photos' map.resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags' map.resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings'

Dobrą wiadomością jest, że jeśli Twój kod okaże się zbyt rozbudowany, możesz go uprościć. Railsy umożliwiają dzielenie zasobów na przestrzenie nazw, aby ułatwić ich rozmieszczenie we własnych katalogach. Oto te same trzy drogi routingu stworzone z wykorzystaniem przestrzeni nazw:

map.namespace(:admin) do |admin| admin.resources :photos, :has_many => { :tags, :ratings} end

Jak widzisz, taka wersja jest znacznie zwięźlejsza, a mimo tego daje takie same efekty. Uzyskasz te same helpery, np. admin_photos_url, korzystający z kontrolera Admin::PhotosController i wskazujący na admin/photos oraz admin_photos_ratings_path, wskazujący na admin/photos/_photo_id_/ratings i korzystający z Admin::RatingsController. Nawet jeśli nie zdefiniujesz bezpośrednio prefiksu path_prefix, wpis routingu spowoduje automatycznie wyliczenie poprawnego prefiksu dla drogi zagnieżdżonej.

3.11 Dodawanie większej liczby akcji RESTful

Nie jesteś ograniczony do siedmiu domyślnie tworzonych dróg routingu. Jeśli chcesz możesz dodawać kolejne drogi członkowskie (member routes; odnoszące się do konkretnego zasobu), a także drogi tworzące zasoby oraz drogi odnoszące się do zestawu zasobów jako całości.

3.11.1 Dodawanie dróg członkowskich

Aby dodać drogę członkowską, użyj opcji :member:

map.resources :photos, :member => { :preview => :get }

Umożliwi to Railsom rozpoznanie adresów URL takich jak /photos/1/preview przy żądaniu HTTP GET i przekazanie ich do akcji preview kontrolera Photos. Stworzy to także helper preview_photo.

Wewnątrz tablicy dróg członkowskich, każda z nich określa typ żądania HTTP, jaki będzie rozpoznawać. Do dyspozycji mamy :get, :put, :post, :delete lub :any. Możesz także określić tablicę metod, jeśli potrzebujesz więcej niż jedną, ale nie chcesz dopuścić dowolnej:

map.resources :photos, :member => { :prepare => [:get, :post] }
3.11.2 Dodawanie dróg do zestawu zasobów

Aby dodać drogę odnoszącą się do zestawu zasobów jako całości, wykorzystywana jest opcja :collection:

map.resources :photos, :collection => { :search => :get }

Taki wpis pozwoli Railsom rozpoznać URL taki jak /photos/search wykorzystujący żądanie HTTP GET i skierować go do akcji search kontrolera Photos. Dodatkowo, stworzony zostanie helper search_photos.

Tak jak przy drogach członkowskich, możesz określić tablicę metod dostępnych dla drogi do zestawu zasobów:

map.resources :photos, :collection => { :search => [:get, :post] }
3.11.3 Dodawanie dróg tworzących zasoby

Aby dodać nową drogę tworzącą zasoby, używamy opcji :new:

map.resources :photos, :new => { :upload => :post }

Taki wpis sprawi, że Railsy rozpoznają URL-e takie jak /photos/upload wykorzystujące żądanie HTTP POST i skieruje je do akcji upload kontrolera Photos. Stworzony zostanie także helper upload_photos.

Jeśli chcesz przedefiniować typ żądania akceptowanego przez jedną ze standardowych akcji, możesz to zrobić wprost poprzez wskazanie tej akcji, np:

map.resources :photos, :new => { :new => :any }

Dzięki temu akcja new będzie mogła być wywołana przez żądanie odnoszące się do photos/new, niezależnie od typu tego żądania.

3.11.4 Uwaga końcowa

Jeśli okaże się, że tworzysz zbyt wiele dodatkowych akcji dla drogi RESTful, czas zastanowić się dlaczego nie chcesz uznać potrzeby stworzenia nowego zasobu, który mógłby przejąć te żądania. W momencie, gdy kolejne opcje :member i :collection zaczynają tworzyć wielkie śmietnisko, routing RESTful traci swoją najważniejszą zaletę, jaką jest czytelność kodu.

4 Drogi regularne

Oprócz routingu RESTful, Railsy obsługują także routing regularny – sposób na przetłumaczenie URL na odpowiednie kontrolery i akcje. Korzystając z tego rodzaju routingu, nie otrzymujesz ogromnej liczby automatycznie wygenerowanych dróg routingu. Zamiast tego, musisz samodzielnie ustawić każdą drogę obsługiwaną przez aplikację.

Chociaż routing RESTful stał się standardem dla frameworku Rails, w wielu wypadkach prostszy routing regularny w zupełności wystarcza. Możesz też łączyć te dwa typy routingu wewnątrz jednej aplikacji. Ogólnie rzecz biorąc, powinno się preferować routing RESTful zawsze, gdy jest to możliwe, ponieważ sprawia on, że aplikacja jest prostsza do napisania. Nie musisz jednak na siłę dopasowywać każdego elementu swojej aplikacji do frameworku w stylu RESTful.

4.1 Podstawowe parametry

Gdy tworzymy drogę regularną, mamy do dyspozycję serię różnych symboli, kojarzonych przez Railsy z elementami nadchodzącego żądania HTTP. Dwa z nich mają szczególne znaczenie: :controller służy do wskazania kontrolera w naszej aplikacji, a :action – do wskazania akcji danego kontrolera. Rozważmy na przykład jedną z domyślnych dróg routingu Rails:

map.connect ':controller/:action/:id'

Jeśli nadchodzące żądanie dotyczące zasobu /photos/show/1 będzie przetworzone przez ten wpis (ponieważ nie pasowało do żadnej wcześniejszej drogi w pliku routes.rb), w rezultacie zostanie wywołana akcja show kontrolera Photo, a parametr params[:id] będzie miał przypisaną wartość 1.

4.2 Komponenty z “dziką kartą”

Możesz określić dowolną liczbę symboli w drodze regularnej. Wszystko inne niż :controller lub :action będzie dostępne dla docelowej akcji w tablicy parametrów. Jeśli więc zdefiniujesz następującą drogę routingu:

map.connect ':controller/:action/:id/:user_id'

Nadchodzący URL /photos/show/1/2 będzie przekierowany do akcji show kontrolera Photos. params[:id] będzie miał ustawioną wartość 1, a params[:user_id] – wartość 2.

4.3 Tekst statyczny

Podczas tworzenia drogi routingu możesz też dodać do niej statyczny tekst. W takim przypadku, obsłużone przez nią będą jedynie żądania, które posiadają dokładnie taką treść:

map.connect ':controller/:action/:id/with_user/:user_id'

Taka droga odpowie na URL typu photos/show/1/with_user/2.

4.4 Parametry łańcucha zapytania (querystring)

Routing Railsów automatycznie pobierze każdy parametr zawarty wewnątrz łańcucha zapytania i udostępni go wewnątrz tablicy asocjacyjnej params. Załóżmy przykładową drogę routingu:

map.connect ':controller/:action/:id'

Nadchodzący URL photos/show/1?user_id=2 zostanie skierowany do akcji show kontrolera Photos. Zmienna params[:id] będzie ustawiona na 1, a dodatkowo dostępna będzie zmienna params[:user_id] o wartości 2.

4.5 Domyślne definicje

Nie musisz używać jawnie symboli :controller i :action wewnątrz drogi routingu. Możesz zawrzeć domyślne wartości tych dwóch parametrów w tablicy asocjacyjnej:

map.connect 'photos/:id', :controller => 'photos', :action => 'show'

Przy takiej drodze routingu, nadchodzący URL /photos/12 zostanie skierowany do domyślnej akcji show kontrolera Photos.

Możesz także określić inne domyślne wartości, korzystając z opcji :defaults. Dotyczy to także parametrów, które nie są zawarte wprost nigdzie w drodze routingu, na przykład:

map.connect 'photos/:id', :controller => 'photos', :action => 'show', :defaults => { :format => 'jpg' }

Przy takiej drodze routingu, nadchodzący URL photos/12 będzie skierowany do akcji show kontrolera Photos z parametrem params[:format] o wartości jpg.

4.6 Drogi nazwane

Drogi regularne nie muszą korzystać z metody connect. Możesz użyć dowolnej innej nazwy, tworząc tym samym drogę nazwaną. Oto przykład:

map.logout '/logout', :controller => 'sessions', :action => 'destroy'

Taki wpis spowoduje dwie rzeczy. Po pierwsze, żądania dotyczące zasobu /logout będą przesłane do metody destroy kontrolera Sessions. Po drugie, Railsy ustanowią helpery logout_path i logout_url.

4.7 Wymagania dróg routingu

Możesz skorzystać z opcji :requirements by wymusić dany format dla każdego z parametrów w drodze:

map.connect 'photo/:id', :controller => 'photos', :action => 'show', :requirements => { :id => /[A-Z]\d{5}/ }

Taka droga routingu zareaguje na nadchodzący URL typy /photo/A12345. Możesz zapisać taką drogę jeszcze krócej:

map.connect 'photo/:id', :controller => 'photos', :action => 'show', :id => /[A-Z]\d{5}/

4.8 Warunki dróg routingu

Warunki dróg (wprowadzane przy użyciu opcji :conditions) zostały stworzone aby wprowadzić ograniczenia na drogach. Obecnie jedynym obsługiwanym warunkiem jest :method:

map.connect 'photo/:id', :controller => 'photos', :action => 'show', :conditions => { :method => :get }

Tak jak w przypadku dróg RESTful, możesz zdefiniować typy żądań :get, :post, :put, :delete lub :any.

4.9 Uogólnianie dróg routingu

Uogólnianie dróg routingu służy wprowadzeniu parametru, który pasuje do wszystkich elementów drogi. Na przykład:

map.connect 'photo/*other', :controller => 'photos', :action => 'unknown',

Taka droga obsłuży zarówno żądanie odnoszące się do photo/12, jak i photo/long/path/to/12, przypisując zmiennej w tablicy asocjacyjnej params[:other] pozostałą część ścieżki.

4.10 Opcje dróg routingu

Możesz użyć opcji :with_options by uprościć definicję grupy podobnych dróg routingu:

map.with_options :controller => 'photo' do |photo| photo.list '', :action => 'index' photo.delete ':id/delete', :action => 'delete' photo.edit ':id/edit', :action => 'edit' end

Znaczenie tej opcji zmalało z momentem wprowadzenia dróg RESTful.

5 Formaty i polecenie respond_to

Istnieje jeszcze jeden sposób, by routing dopasowywał odpowiedź aplikacji na podstawie różnic w nadchodzącym żądaniu HTTP: poprzez tworzenie odpowiedzi w zależności od tego jaki format jest akceptowalny dla żądania. W routingu Rails, możesz kontrolować tę opcję korzystając w definicji drogi routingu z parametru :format.

Załóżmy na przykład, że druga z domyślnych dróg w pliku routes.rb ma postać:

map.connect ':controller/:action/:id.:format'

Taka droga routingu będzie obsługiwała żadania takie jak /photo/edit/1.xml lub /photo/show/2.rss. Wewnątrz właściwego kodu akcji, możemy wymusić różne zachowanie w zależności od żądanego formatu:

respond_to do |format| format.html # return the default template for HTML format.xml { render :xml => @photo.to_xml } end

5.1 Rozróżnianie formatu na podstawie nagłówka żądania HTTP

Jeśli parametr :format nie występuje w definicji drogi routingu, Railsy automatycznie uznają zawartość nagłówka HTTP Accept za żądany format odpowiedzi.

5.2 Rozpoznawalne typy MIME

Domyślnie Railsy rozpoznają typy html, json, csv, xml, rss, atom i yaml jako akceptowalne formaty odpowiedzi. Jeśli potrzebujesz innego typu, możesz go zdefiniować w konfiguracji środowiska:

Mime::Type.register "image/jpg", :jpg

6 Drogi domyślne

Podczas tworzenia nowej aplikacji w Railsach, plik routes.rb jest uzupełniany dwoma domyślnymi drogami routingu:

map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'

Te drogi zapewniają rozsądną obsługę dla wielu URL, pod warunkiem że nie używasz routingu RESTful.

Domyślne drogi routingu sprawią, że każda akcja każdego kontrolera Twojej aplikacji będzie osiągalna za pomocą żądania typu GET. Jeśli planujesz korzystać z dróg RESTful i dróg nazwanych, powinieneś “wykomentować” drogi domyślne, by uniemożliwić dostęp do kontrolerów żądaniami niechcianych typów. Jeśli jednak na etapie tworzenia aplikacji domyślne drogi routingu będą aktywne, musisz upewnić się, że nie wykorzystujesz ich gdzieś przypadkowo – w przeciwnym wypadku po ich wyłączeniu możesz liczyć się z dziwnym zachowaniem aplikacji.

7 Pusta droga routingu

Nie należy mylić drogi domyślnej z drogą pustą. Ta ostatnia ma jedno unikalne założenie: służy do obsługi żądań odnoszących się do katalogu głównego strony. Jeśli twoja strona ma adres example.com, to żądania do http://example.com lub http://example.com/ będą obsługiwane przez drogę pustą.

7.1 Używanie map.root

Preferowanym sposobem obsługi drogi pustej jest użycie komendy map.root:

map.root :controller => "pages", :action => "main"

Użycie metody root zasygnalizuje Railsom, że droga obsługuje żądania dotyczące katalogu głównego strony.

Dla poprawy czytelności, możesz zdefiniować w wywołaniu map.root stworzoną wcześniej drogę nazwaną:

map.index 'index', :controller => "pages", :action => "main" map.root :index

Z powodu ustalonej kolejności przetwarzania pliku routes.rb, droga nazwana musi zostać określona przed odwołaniem map.root.

7.2 Łączenie pustego łańcucha znaków

Możesz też określić pustą drogę wprost, poprzez dopasowanie do pustego ciągu znaków:

map.connect '', :controller => "pages", :action => "main"

Jeśli masz wrażenie, że pusta droga nie działa w Twojej aplikacji poprawnie, upewnij się, że usunąłeś plik public/index.html z drzewa katalogów.

8 Inspekcja i testowanie dróg routingu

Routowanie nie powinną być “czarną skrzynką” aplikacji, do której lepiej nigdy nie zaglądać. Railsy oferują wbudowane narzędzia, służące do inspekcji i testowania dróg.

8.1 Przelądanie istniejących dróg routingu poprzez Rake

Jeśli potrzebujesz kompletnej listy wszystkich dostępnych w aplikacji dróg routingu, użyj komendy rake routes. Zrzuci ona wszystkie drogi na wyjście konsoli, zachowując kolejność z jaką są one wpisane w pliku routes.rb. Wśród informacji dotyczących każdej drogi zobaczysz:

  • jej nazwę ścieżki (o ile została podana)
  • typ żądania HTTP (o ile droga nie odpowiada na wszystkie typy żądań)
  • wzorzec URL
  • parametry generowane przez ten URL

Dla przykładu, oto mały wycinek wyników polecenia rake routes dla drogi RESTful:

users GET /users {:controller=>"users", :action=>"index"} formatted_users GET /users.:format {:controller=>"users", :action=>"index"} POST /users {:controller=>"users", :action=>"create"} POST /users.:format {:controller=>"users", :action=>"create"}

Z pewnością przekonasz się, że wyniki komendy rake routes są o wiele bardziej czytelne gdy poszerzysz okno terminala.

8.2 Testowanie dróg routingu

Drogi routingu powinny być stałym punktem Twojej strategii testowania strony (podobnie jak reszta aplikacji). Railsy oferują trzy wbudowane narzędzia służące uproszczeniu tej procedury:

  • assert_generates
  • assert_recognizes
  • assert_routing
8.2.1 Assert_generates

Używamy assert_generates dla upewnienia się, że dany zestaw opcji wygeneruje wymaganą ścieżkę. Dotyczy to dróg domyślnych i dróg posiadających dodatkowe opcje:

assert_generates "/photos/1", { :controller => "photos", :action => "show", :id => "1" } assert_generates "/about", :controller => "pages", :action => "about"
8.2.2 Assert_recognizes

Assert_recognizes jest przeciwieństwem assert_generates. Sprawdza on, czy Rails rozpoznaje wskazaną ścieżkę i kieruje ją do wymaganego elementu aplikacji.

assert_recognizes { :controller => "photos", :action => "show", :id => "1" }, "/photos/1"

Możesz użyć dodatkowego argumentu :method, by sprecyzować typ żądania HTTP:

assert_recognizes { :controller => "photos", :action => "create" }, { :path => "photos", :method => :post }

Możesz także użyć helperów RESTful dla rozpoznania ścieżki drogi RESTful:

assert_recognizes new_photo_url, { :path => "photos", :method => :post }
8.2.3 Assert_routing

Assert_routing sprawdza obydwa aspekty routingu: sprawdza czy ścieżka wywołuje odpowiednie opcje oraz czy opcje generują odpowiednią ścieżkę. W zasadzie polega to na połączeniu funkcji assert_generates i assert_recognizes.

assert_routing { :path => "photos", :method => :post }, { :controller => "photos", :action => "create" }

9 Changelog

Lighthouse ticket

  • October 4, 2008: Added additional detail on specifying verbs for resource member/collection routes , by Mike Gunderloy
  • September 23, 2008: Added section on namespaced controllers and routing, by Mike Gunderloy
  • September 10, 2008: initial version by Mike Gunderloy