homeASCIIcasts

203: Routing in Rails 3 

(view original Railscast)

Other translations: En Cn Es

Other formats:

Written by Andrea Salicetti

Contiunuiamo il nostro viaggio alla scoperta delle nuove funzionalità di Rails 3 in questo episodio, concentrandoci sul routing. Rails 3 ha una nuova API per definire le regole di routing ed alcune nuove funzionalità. Per mostrarvi come funzionano, creiamo una nuova applicazione chiamata detour e definiamo alcuni instradamenti per quest’ultima:

rails detour

Creata l’applicazione, diamo un’occhiata al file config/routes.rb. Il file generato contiene un po’ di documentazione e alcuni esempi di nuove API: non fa male darci uno sguardo. Ora cambieremo il contenuto di default con un esempio di alcuni vecchi instradamenti stile Rails 2 per vedere come cambiano in Rails 3:

/config/routes.rb

Detour::Application.routes.draw do |map|
  map.resources :products, :member => { :detailed => :get }

  map.resources :forums, :collection => { :sortable => :get, :sort => :put } do |forums|
    forums.resources :topics
  end

  map.root :controller => "home", :action => "index"

  map.about "/about", :controller => "info", :action => "about"
end

Gli instradamenti vecchio stile che stiamo per convertire.

Partiamo dal primo instradamento:

map.resources :products, :member = { :detailed => :get }

Questo instradamento ha una risorsa products e una action specifica member chiamata detailed che viene invocata mediante una richiesta GET.

Il primo cambiamento da notare nel meccanismo di routing di Rails 3 è che non trattiamo più con l’oggetto map, ma piuttosto chiamiamo direttamente resources nel blocco routes.draw. Le azioni :member e :collection dentro a resources sono definite dentro un blocco. Il route può perciò essere riscritto in questo modo:

resources :products do
  get :detailed, :on => :member
end

Ora guardiamo un instradamento più complicato:

map.resources :forums, :collection => { :sortable => :get, :sort => :put } do |forums|
  forums.resources :topics
end

In questo route abbiamo una risorsa forum con due utleriori elementi collezioni e una risorsa innestata topics. Con la nuova API questo instradamento può essere scritto così:

resources :forums do
  collection do
    get :sortable
    put :sort
  end
  resources :topics
end

Ancora, usiamo resources anzichè map.resources e passiamo un blocco. Abbiamo due azioni collection nel route. Benchè potremmo definire quelle allo stesso modo in cui abbiamo fatto con la azione detailed nel primo route, facendo uso dell’argomento :on, in questo caso definiremo un blocco collection (i membri possono essere trattati allo stesso modo che in un blocco member) e ogni instradamento definito dentro la blocco agirà sulla collezione di forum. Nel nostro blocco abbiamo definito due nuove action: sortable come richiesta di tipo GET e sort come richiesta di tipo PUT.

Per la risorsa innestata topics, chiamiamo nuovamente solo resources, innestando la risorsa topics dentro a forums.

Il prossimo route che vediamo è un route che definisce il controller e l’action a cui deve puntare l’URL di root:

map.root :controller => "home", :action => "index"

Qui chiamiamo semplicemente root e usiamo un argomento :to a cui passiamo una stringa che contiene il nome del controller e dell’action separati dal carattere #.

root :to => "home#index"

Questa possibilità di specificare sia il controller, sia la action in un’unica stringa è una nuova feature di Rails 3. Possiamo fare qualcosa di simile per un instradamento con nome:

map.about "/about", :controller => "info", :action => "about"

Con l’API di Rails 3 tutto ciò può essere riscritto come:

match "/about" => "info#about", :as => :about

Senza l’argomento :as l’instradamento di sopra sarebbe stato solo un instradamento generico. L’aggiunta di :as lo rende un instradamento con nome, in modo tale che in seguito si possano usare riferimenti ad URL del tipo about_path o about_url.

Nuove funzionalità

Come si può vedere dagli esempi sopra, non è difficile convertire gli instradamenti dalla vecchia API alla nuova, ma ciò che rende realmente interessante la nuova API di routing sono le nuove funzionalità che mette a disposizione e che vedremo ora nel resto dell’episodio.

Parametri opzionali

I parametri opzionali erano già supportati dalle precedenti versioni di Rails, ma la sintassi era un po’ scomoda. Vediamo cosa è cambiato invece in Rails 3.

Sarà più semplice dimostrare l’utilizzo dei parametri opzionali se la nostra applicazione ha un controller, per cui creiamone uno. Creiamo un info controller con una action about. Si noti che in Rails 3 possiamo usare rails g come scorciatoia per il comando rails generate:

rails g controller info about

Possiamo avviare il server con un altro nuovo shortcut:

rails s

Ora, se visitiamo http://localhost:3000/about vedremo la action info#about come è stato imposto dall’instradamento che abbiamo scritto poc’anzi.

La action about.

Una volta sistemato questo, vogliamo ora fornire una versione PDF per questa action, ma se visitiamo l’URL http://localhost:3000/about.pdf otteniamo un errore di routing poichè la nostra applicazione non sa come risolvere questo instradamento.

Nel file di route possiamo cambiare l’instradamento di about per accettare un parametro di formato, aggiungendo un . e un :format:

match "/about.:format" => "info#about", :as => :about

Se ricarichiamo la vista PDF ora, il route sarà risolto, ma avremo un errore per mancanza di template, in quanto la nostra applicazione non sa come renderizzare quella action come PDF.

L’instradamento PDF è ora risolto.

Sembra che abbiamo risolto il problema, ma il formato che abbiamo appena aggiunto alle regole di routing non è opzionale, per cui quando torniamo indietro alla vista di default per quella pagina, otteniamo un errore di routing. Fortunatamente è facile rendere una parte del route opzionale: tutto ciò che occorre è racchiudere la parte opzionale fra parentesi, in questo modo:

match "/about(.:format)" => "info#about", :as => :about

Ora possiamo visitare sia http://localhost:3000/about, sia http://localhost:3000/about.pdf senza vedere errori di routing.

Ora vi mostreremo come usare parametri opzionali più complessi. Ipotizziamo che la nostra applicazione abbia un certo numero di articoli di blog che vorremmo filtrare specificando un anno con un mese opzionale (o mese e giorno) nell’URL.

Possiamo fare ciò definendo un instradamento tipo questo, dirigendo ogni instradamento che fa match alla nostra action info#about. Si noti come sia possibile innestare le parentesi quando si creano parametri opzionali:

match "/:year(/:month(/:day))" => "info#about"

Nel codice della vista aggiungiamo un po’ di codice di debug in modo tale da poter vedere quali parametri vengano passati:

/app/views/info/about.html.erb

<h1>Info#about</h1>
<p>Find me in app/views/info/about.html.erb</p>
<%= debug params %>

Ora, se indichiamo un anno nell’URL, sarà passato alla action about e analogamente, se specifichiamo un anno, un mese e un giorno, tutti e tre i parametri verranno passati.

I parametri di data sono passati al route.

Questo instradamento è comunque abbastanza generico e se volessimo visitare, per esempio, http://localhost:3000/foo/bar, anche questo URL sarebbe servito dalla action about, anche se noi in realtà vorremmo che solo i parametri che sembrino date siano passati a tale action. Possiamo vare ciò che vorremmo con i vincoli.

Vincoli

I vincoli sono disponibili già da Rails 2, conosciuti col nome di requirements. Possiamo passare un opzione :constraints al nostro route con un hash di valori ed i vincoli che ciascun valore dovrebbe rispettare. Vincoliamo dunque il route in modo tale che faccia match solo se il parametro anno è di quattro numeri ed il mese e il giorno sono di due numeri:

match "/:year(/:month(/:day))" => "info#about", :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }

Con questo vincolo, quando visitiamo il percorso /foo/bar, ora otterremo un errore di routing, dal momento che, come volevamo, il percorso specificato non fa più match con i vincoli sulle date.

L’instradamento ora non fa match se non con i parametri di tipo data.

Tutto ciò che abbiamo fatto fino ad ora con i vincoli era possibile farlo anche in Rails 2 con i requirements, ma con i vincoli di Rails 3 possiamo in relatà fare molto di più. Per esempio, possiamo usare un parametro user_agent per restringere il route solo a determinati browser, in questo caso Firefox:

match "/secret" => "info#about", :constraints => { :user_agent => /Firefox/ }

Se visitiamo http:/localhost:3000/secret da Safari, Chrome o Opera, vedremo un errore di routing, ma se lo visitiamo da Firefox, o fingiamo con un altro browser di essere Firefox (modificando di fatto lo user-agent nell’head della richiesta HTTP), allora lo user agent passato dal browser farà match col vincolo e la pagina verrà mostrata:

L’instradamento sarà risolto solo se visto da Firefox

Potremmo anche aggiungere un vincolo per qualcosa di un po’ più utile, per esempio l’host:

match "/secret" => "info#about", :constraints => { :host => /localhost/ }

Questo vincolo significa che possiamo visitare http://localhost:3000/secret e vedere la pagina, ma otteniamo un errore di routing se proviamo a visitare la pagina usando l’indirizzo IP anzichè localhost( http://127.0.0.1/secret ) nonostante che entrambi siano logicamente equivalenti. Questo trucco può essere utilizzato per restringere determinati instradamenti solo a specifici sottodomini. Al momento il modo per fare ciò è un po’ scomodo, ma in una prossima versione di Rails saremo in grado di passare una opzione :subdomain per definire questo genere di vincoli.

Se abbiamo un numero di instradamenti che devono fare match con un certo vincolo, potremmo ritrovarci con un po’ di duplicazioni di codice nel file di route. Si può ovviare al problema, in questi casi, usando il metodo constraints e inserendo in un blocco gli instradamenti che devono fare match:

constraints :host => /localhost/ do
  match "/secret" => "info#about"
  match "/topsecret" => "info#about"
end

Ci sono molte altre cose che si possono fare con l’opzione constraint, ma non esauriremo tutte le potenzialità del comando qui.

Instradamenti con Rack

C’è molto di più sul routing in Rails 3 di quanto non abbiamo il tempo di far vedere in questo episodio, ma ci ritorneremo su negli episodi futuri. Un’ultima funzionalità a cui diamo uno sguardo è il modo in cui il routing di Rails 3 si concilia con Rack. Normalmente passiamo un controller e il nome di una action ad un route, ma possiamo anche passare una applicazione Rack. Per dimostrarlo, creiamo un instradamento che punti ad una semplice applicazione Rack.

match "/hello" => proc { |env| [200, {}, "Hello Rack!"] }

Se non conoscete Rack, guardatevi l’episodio 151. Tutto ciò che stiamo facendo qui, è semplicemente di passare un codice di ritorno, un hash (vuoto) di headers e un semplice body.

Visitando /hello dal browser ora, vediamo “Hello Rack!”, che significa che la nostra applicazione Rack sta funzionando e generando la risposta. Questo apre la strada ad una enorme potenzialità e flessibilità del modo in cui si definiscono gli instradamenti e li si inoltrano verso applicazioni differenti. E’ora semplice l’instradamento verso una applicazione Sinatra, per esempio.

La risposta viene mostrata dalla applicazione Rack.

Le nuove funzionalità di routing di Rails 3 forniscono una serie di interessanti possibilità. Anche se qui abbiamo fornito solo uno sguardo panoramico del potenziale, torneremo più in dettaglio sui singoli argomenti negli episodi futuri.

Per maggior documentazione sul routing di Rails 3, date un’occhiata al blog di Yehuda Katz e alle RailsGuides.