#224 Controllers in Rails 3
- Download:
- source codeProject Files in Zip (95.2 KB)
- mp4Full Size H.264 Video (19.7 MB)
- m4vSmaller H.264 Video (12.4 MB)
- webmFull Size VP8 Video (30.9 MB)
- ogvFull Size Theora Video (28.7 MB)
Sebbene la maggior parte delle modifiche ai controller in Rails 3 sono avvenute dietro le quinte in modo trasparente, ci sono alcune differenze nell’interfaccia con cui potremmo avere a che fare come sviluppatori Rails. Questo episodio coprirà alcune fra le nuove funzionalità aggiunte in questa nuova release del framework.
Logging dei parametri
La prima modifica che ispezionaremo è nell’ApplicationController
. In un’applicazione Rails 2 si poteva evitare che taluni parametri fossero tracciati in chiaro nei file di log, usando il metodo filter_parameter_logging
, come mostrato qui:
class ApplicationController < ActionController::Base protect_from_forgery filter_parameter_logging :password end
Questo metodo è stato deprecato in Rails 3 e dovrà essere rimosso. Il filtraggio dei parametri viene ora imposto nel file di configurazione /config/application.rb
. Il codice che configura i parametri che devono essere mascherati è in fondo al file:
# Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password]
Questa linea viene aggiunta di default nelle nuove applicazioni Rails 3. E’ facile dimenticarsi di aggiungere un filtraggio per taluni parametri "sensibili", per cui è utile avere già un simile default. Naturalmente, così com’è, filtrerà solamente i parametri password e dovrà essere modificata se esistono ulteriori parametri, come ad esempio il numero di carta di credito, che devono essere mascherati.
Scorciatoie per il redirect
Ora diamo un’occhiata al ProductsController
nell’applicazione del negozio on-line che abbiamo scritto e a come i redirect siano gestiti alla chiamata di una action tipo create
o update
in un tipico controller Rails con i soliti sette metodi RESTful. Se guardiamo il codice del metodo create
, vedremo che una volta che un Product
valido è stato salvato, la action imposta un messaggio flash
e poi ridirige alla action show
per il prodotto appena creato:
def create @product = Product.new(params[:product]) if @product.save flash[:notice] = "Successfully created product." redirect_to @product else render :action => 'new' end end
E’ un comportamento piuttosto comune in un controller e a partire da Rails 3, possiamo unire queste due linee in modo tale che la notifica flash
divenga un parametro del metodo redirect_to
:
def create @product = Product.new(params[:product]) if @product.save redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
Sia :notice
sia :alert
funzionerebbero in questo caso, ma qualsiasi altro tipo di messaggio flash
dovrebbe essere incluso in un hash :flash
. Questa funzionalità era stata già aggiunta in Rails 2.3.6, per cui se state già usando l’ultima versione di Rails 2, potete già anche usare questa scorciatoia.
Il metodo redirect_to
qui sopra accetta un oggetto di modello come parametro. Il passaggio di un oggetto di modello Product
mediante @product
in questo caso rappresenta uno shortcut per product_path(@product)
e viene tradotto nel percorso per la action show
per tale prodotto. Se invece volessimo ridirigere alla pagina di edit, potremmo utilizzare edit_product_path(@product)
, ma in realtà esiste una scorciatia più elegante che possiamo sfruttare in questi casi. Anzichè scrivere:
redirect_to edit_product_path(@product)
possiamo scrivere:
redirect_to [:edit, @product]
Questa linea verrà tradotta nello stesso percorso visto prima, ma scrivendo meno codice. Analogamente all’opzione :notice
, questa scorciatoia è disponibile già nelle ultime versioni di Rails 2. Funziona anche con le risorse annidate, per cui se un prodotto appartiene ad una categoria, potremmo ridirigere con:
redirect_to [@category, @product]
Questa tecnica ad array funziona ovunque sia possibile generare un URL e può essere usata nel codice delle viste in un metodo link_to
, esattamente come nei controller.
Permanent Cookies
La prossima nuova feature che vi mostreremo è denominata permanent cookies. Poniamo il caso di voler salvare su un cookie l’id
di un prodotto appena creato. Possiamo usare il metodo cookies
per farlo:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
Questo codice crea un cookie di sessione che sarà mantenuto fintanto che l’utente manterrà la finestra del proprio browser aperta. Per creare un cookie con una data di scadenza esplicita, in Rails 2 avremmo dovuto passare il valore di tale data in un hash in questo modo:
cookies[:last_product_id] = { :value => @product_id, :expires => 1.month.from_now }
In Rails 3 (e già anche da Rails 2.3.6) possiamo usare il nuovo metodo cookies.permanent
per creare un cookie con un tempo di scadenza più lungo rispetto alla finestra del browser.
cookies.permanent[:last_product_id] = @product.id
Possiamo anche creare dei cookie firmati in questo modo e, sebbene non approfondiremo la cosa in questa sede, esiste un ottimo post nel blog di Pratik Naik su questo argomento che vale la pena di leggersi. In breve, possiamo usare il metodo cookies.permanent.signed
per generare un cookie cifrato e leggerlo in un secondo tempo. Si noti che dovremo avere impostato il cookie_verifier_secret
nella nostra applicazione, affinchè il tutto funzioni correttamente.
Utilizzo della respond_with
L’ultima cosa che vi mostreremo in questo episodio è probabilemente l’aggiunta più significativa ai controller fatta da Rails 3: respond_with
. Vi mostreremo il funzionamento di questo nuovo metodo aggiungendo un servizio REST su di un interfaccia XML al nostro ProductsController
. Ogni action del controller dovrà restituire una risposta XML in aggiunta alla classica risposta HTML.
In un’applicazione Rails 2 avremmo implementato il tutto usando una respond_to
in ogni action, così:
def index @products = Product.all respond_to do |format| format.html format.xml { render :xml => @products } end end
All’interno del blocco della respond_to
, avremmo specificato tutti i formati previsti per la risposta di quella specifica action, renderizzando la versione XML dell’array dei prodotti nel caso di una richiesta XML. Questo approccio può diventare rapidamente scomodo se andasse applicato ad ogni action del controller.
In Rails 3 si può sostituire questo codice con una sola chiamata alla respond_with
, passando l’oggetto opportuno, in questo caso l’array di prodotti. Affinchè la respond_with
funzioni, dobbiamo anche utilizzare il metodo di classe respond_to
per indicare a quali tipi MIME il controller deve essere in grado di rispondere ed affinchè la respond_with
sappia cosa rispondere in ogni action:
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end end # Other methods end
Ora che abbiamo cambiato il nostro ProductsController
per fargli utilizzare la respond_with
, vediamo se le modifiche hanno funzionato. Se visitiamo la pagina indice dei prodotti in un browser, vedremo renderizzata la action index HTML, come ci saremmo aspettati:
E se aggiungiamo .xml
all’URL, otteniamo la risposta XML:
Come funziona il metodo respond_with
? Per una semplice richiesta GET come index
, cerca una vista per l’opportuno tipo MIME, come ad esempio un file .html.erb
, e la renderizza. Se la respond_with
non riesce a trovare un file per la vista, prova ad invocare il metodo to_xml
o to_json
(o to_qualunque altro formato richiesto), per renderizzare l’oggetto che è stato passato nel formato appropriato.
Sapendo tutto ciò possiamo aggiornare i metodi show
e new
nel ProductsController
per far loro utilizzare la respond_with
:
def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end
L’action create
è più complessa, dal momento che presenta del codice specifico per l’HTML al suo interno, come ad esempio chiamate al redirect_to
e al render
. Non vogliamo chiaramente che queste chiamate siano eseguite nel caso di risposte diverse dall’HTML, ma possiamo tranquillamente superare questi ostacoli semplicemente rimuovendo tale codice dal metodo ed aggiungendo una chiamata al respond_with
:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end
Lo possiamo fare, dal momento che la respond_with
ridirigerà automaticamente o renderizzerà, a seconda del fatto che l’oggetto passatogli sia valido o meno. Ora possiamo aggiungere il respond_with
agli altri metodi del controller, rimuovendo tutto il codice specifico per l’HTML. Quando avremo finito, il codice del nostro controller dovrebbe risultare più o meno così:
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end def edit @product = Product.find(params[:id]) respond_with(@product) end def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product) end def destroy @product = Product.find(params[:id]) @product.destroy flash[:notice] = "Successfully destroyed product." respond_with(@product) end end
Il metodo respond_with
potrebbe risultare un tantino ambiguo e confondere un po’, per cui facciamo un piccolo riassunto del suo funzionamento.
Per una richiesta GET, il respond_with
cercherà per prima cosa una vista in grado di rispondere allo specifico formato richiesto. Se la trova, la renderizza; altrimenti, chiamerà il metodo to_xml
(o l’analogo to_xxx
relativo al formato richiesto) sull’oggetto passatogli.
Per ogni altro tipo di richiesta, il respond_with
per prima cosa controlla che l’oggetto passato non abbia degli errori (di validazione). Se ne ha, viene renderizzata la vista appropriata (ad esempio new
per l’action create
e edit
per quella di update
). Se invece non ce ne sono, ridirige alla pagina specifica di quell’oggetto (es. la action show
).
Possiamo provare il tutto modificando uno dei nostri prodotti:
Al click del pulsante “Update Product”, viene invocata la action update
ed il respond_with
eseguirà un redirect alla pagina di quel prodotto:
Analogamente, se clicchiamo il link “destroy”, saremo ridiretti alla pagina di indice:
Se il comportamento di default del respond_with
non va bene con talune parti della vostra applicazione, è sempre possibile personalizzarlo per certe action specifiche. Per esempio, poniamo di voler riportare l’utente alla pagina di indice dei prodotti a seguito dell’aggiornamento di un singolo prodotto. In questo caso, possiamo passare una opzione location al respond_with
in modo tale che ci ridiriga ad una action differente. Per essere ridiretti alla pagina index dei prodotti, anzichè alla pagina di dettaglio del prodotto appena aggiornato, possiamo cambiare la action di update nel seguente modo:
def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product, :location => products_url) end
Possiamo anche fare l’override del formato specifico, passando un blocco al respond_with
proprio come faremmo con una respond_to
. Per cui, se volessimo solo renderizzare del testo per il formato XML dell’action di edit, potremmo fare così:
def edit @product = Product.find(params[:id]) respond_with(@product) do |format| format.xml { render :text => "I'm XML!" } end end
Quando andiamo a puntare alla versione XML della pagina di edit, ora vedremo che viene mostrata una stringa:
Infine, se avessimo bisogno di una personalizzazione completa, potremmo passare una classe responder totalmente specializzata. In tal modo, il controller potrebbe gestire tutto il comportamento attraverso quell’oggetto ed il comportamento delle risposte potrebbe essere gestito a proprio completo piacimento:
respond_with(@product, :responder => MyResponder)
Per un esempio su come creare una classe Responder, si dia un’occhiata alla classe Responder inclusa in Rails 3. Il codice è piuttosto semplice da leggere ed è ben documentato e, se non altro, vi farete una idea più chiara sul funzionamento dei responder.
E’ tutto per questo episodio. Come potete vedere, il respond_with
è un modo davvero pratico per gestire formati multipli. Se vi rendete conto che state cominciando a personalizzare un po’ troppo il comportamento standard, allora è possibile che convenga spostare tutte le logiche in una classe responder propria.