Si dovrebbe tenere a mente che Rails è un framework con opinioni e uno dei suoi principi fondamentali è la convenzione sulla configurazione. Questo significa che, a meno che non abbiate una buona ragione, non dovreste infrangere nessuno dei pattern e delle convenzioni di denominazione di cui sopra.
Per esempio, se decidessi di codificare l’azione index come get 'cars/index'
o l’azione create come post 'car/create'
, questo infrangerebbe il comportamento out-of-the-box di Rails per metodi helper come form_for, link_to e redirect_to.
La conseguenza di rompere le convenzioni senza intenzione è il caos e produce una confusione ingarbugliata che vi farà combattere contro il framework per ore e ore.
Rotte per risorse singolari
È anche possibile avere rotte per risorse singolari, cioè risorse che possono essere cercate senza specificare un :id
, come quelle che si applicano a un current_user
o current_account
. Questo può essere ottenuto usando le risorse singolari incorporate di Rails.
resource :profile
Rotte annidate
A volte abbiamo bisogno di annidare risorse dentro altre risorse. Per esempio, se voglio creare una prenotazione per una specifica macchina nel mio garage, potrebbe essere più conveniente prendere il :id
per quella macchina dall’URL piuttosto che un campo nascosto del modulo, che potrebbe essere manomesso da un utente malintenzionato. Il modo in cui creiamo risorse annidate in rails è il seguente:
resources :cars do
resources :bookings
end
Questo crea tutte e sette le azioni CRUD per le prenotazioni annidate nelle auto. Di solito, però, non avete bisogno di tutte e sette le azioni. E anche quando lo fate, certamente non tutti hanno bisogno di essere annidati. Diciamo che ciò di cui abbiamo bisogno è l’azione per creare una prenotazione (assumeremo che il modulo per creare una prenotazione viva nell’azione show dell’auto) e per modificare e aggiornare una prenotazione esistente. Ho bisogno di conoscere il :id
di un’auto per modificare/aggiornare una prenotazione? La risposta è ovviamente no. Pertanto, voglio che la mia applicazione risponda ai seguenti URL:
# to CREATE a booking
POST request to http://my-ruby-garage.com/cars/:id/bookings/create# to EDIT a booking
GET request to http://my-ruby-garage.com/bookings/:id/edit# to UPDATE a booking
PATCH request to http://my-ruby-garage.com/bookings/:id/
Che sono generati dal seguente codice:
resources :cars do
resources :bookings, only:
end
resources :bookings, only:
Rails ci permette di modificare quali percorsi CRUD standard devono essere generati fornendo un array di simboli o un singolo simbolo all’opzione :only
o al suo opposto :except
.
Rotte non CRUD
Non siamo limitati ai sette percorsi delle risorse CRUD. Possiamo anche specificare delle rotte che si applicano a una singola risorsa (rotte membro) o a più risorse (rotte collezione).
Rotte membro
Continuando con il nostro esempio RubyGarage, le auto possono essere parcheggiate o rimosse dal garage. Immaginiamo di avere delle azioni del controller in grado di eseguire queste azioni che modificano qualche attributo di un’auto specifica.
resources :cars do
member do
patch :park
patch :remove
end
end
Quanto sopra ci permette di inviare richieste di patch a http://my-ruby-garage.com/cars/:id/park
e http://my-ruby-garage.com/cars/:id/remove
e trovare l’auto specifica nel controller prima di modificare la risorsa di conseguenza.
Collection routes
Nello stesso modo, a volte vogliamo eseguire qualche azione su diverse risorse allo stesso tempo. Per esempio, forse abbiamo bisogno di parcheggiare e rimuovere una collezione di auto in una volta sola. Possiamo usare il seguente codice:
resources :cars do
collection do
post :park, as: :bulk_park
post :remove, as: :bulk_remove
end
end
Qui, impostiamo la nostra applicazione per rispondere a http://my-ruby-garage.com/cars/park
e http://my-ruby-garage.com/cars/remove
e, rispettivamente, chiamiamo queste azioni bulk_park e bulk_remove. Ricordate che possiamo usare i named path per generare i percorsi URL all’interno della nostra applicazione. Per costruire un percorso di collegamento per parcheggiare una collezione di auto, potremmo usare:
<%= link_to "Park Cars", bulk_park_path, method: :post, class: "button" %>
Namespaced Routes
Namespaced routes prefissa il percorso URL delle risorse all’interno del blocco namespace e cercherà di localizzare i relativi controller sotto un modulo con lo stesso nome del namespace. Usi tipici di questo pattern sono i namespace admin e i namespace api.
namespace :factory do
resources :cars
end
Questo esempio costruisce le seguenti rotte:
Prefix Verb URI Pattern Controller#Action factory_cars GET /factory/cars(.:format) factory/cars#index
POST /factory/cars(.:format) factory/cars#create factory_car GET /factory/cars/:id(.:format) factory/cars#show
PATCH /factory/cars/:id(.:format) factory/cars#update
PUT /factory/cars/:id(.:format) factory/cars#update
DELETE /factory/cars/:id(.:format) factory/cars#destroy
E anche il controller dovrebbe essere namespaced :
class Factory::CarsController < ApplicationController
# ...
end
Scoped Routes
Il metodo scope
ci permette di rimanere DRY e raggruppare regole di routing correlate. Quando è usato senza opzioni, è simile a namespace
, ma i relativi controller non devono essere namespace con un modulo.
scope :factory do
resources :cars
end
Genera i seguenti percorsi:
Prefix Verb URI Pattern Controller#Action factory_cars GET /factory/cars(.:format) cars#index
POST /factory/cars(.:format) cars#create factory_car GET /factory/cars/:id(.:format) cars#show
PATCH /factory/cars/:id(.:format) cars#update
PUT /factory/cars/:id(.:format) cars#update
DELETE /factory/cars/:id(.:format) cars#destroy
Scope supporta tre opzioni: module, path e as.
Diciamo che abbiamo un wizard multi-step per creare una nuova auto in fabbrica che è gestito da un controller che vive in un modulo wizards. Vogliamo che il percorso appaia nel browser come http://my-ruby-garage.com/create-a-car
e che si possa fare riferimento a questo percorso all’interno della nostra applicazione come create_car
. C’è un modulo Wizard::Car
che conosce ogni passo della procedura guidata.
scope module: 'wizards', path: 'create-a-car', as: 'create_car' do
Wizard::Car::STEPS.each do |step|
get step, to: "cars##{step}"
end
post :validate-step, to: 'cars#validate_step'
end
Il codice di cui sopra crea lo stesso schema per ogni passo. Per esempio, il passo1 è accessibile nel browser tramite l’URL http://my-ruby-garage.com/create-a-car/step1
, il suo modulo corrispondente invia una richiesta di post a http://my-ruby-garage.com/create-a-car/validate-step
, e il percorso può essere richiamato chiamando create_car_step1_path
.
Route Redirects
Rails ci permette anche di fare redirects direttamente in routes.rb
. Nell’esempio precedente, forse voglio che chiunque atterri su http://my-ruby-garage.com/create-a-car
sia automaticamente reindirizzato al primo passo. Questo può essere realizzato con il seguente codice:
get 'create-a-car',
to: redirect("/create-a-car/#{Wizard::SpotAccount::STEPS.first}")
Route Defaults
Puoi definire parametri predefiniti in una rotta passando un hash per l’opzione :defaults
.
resources :cars do
collection do
get :export, defaults: { format: 'csv' }
end
end
Utilizzando questo, la visita http://my-ruby-garage.com/cars/export
chiamerà l’azione di esportazione nel controller delle auto e l’azione del controller corrispondente risponderà con un csv per default.
Route Globbing
Utilizzando i segmenti jolly (frammenti preceduti da una stella), possiamo specificare che le parti rimanenti di una rotta devono essere abbinate a un particolare parametro. Questo si chiama route globbing.
get '/rent/*slugs', to: 'cars#index', as: :rent_cars
Questa rotta corrisponde a http://my-ruby-garage.com/rent/lisbon/suv-sedan
e imposta i parametri su “lisbon/suv-sedan”. Questo potrebbe poi essere usato in un sistema di ricerca o di filtraggio nel nostro database per trovare auto a Lisbona del tipo suv o sedan.
Sto scrivendo un gem Slugcatcher con questa funzionalità in mente al fine di taggare i modelli Rails che possono essere cercati come route slugs.