homeASCIIcasts

224: Controller in Rails 3 

(view original Railscast)

Other translations: En Pt Es De

Written by Andrea Salicetti

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:

/app/controllers/application_controller.rb

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:

/config/application.rb

# 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:

/app/controllers/products_controller.rb

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:

/app/controllers/products_controller.rb

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:

/app/controllers/proucts_controller.rb

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ì:

/app/controllers/products_controller.rb

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:

/app/controllers/products_controller.rb

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:

La pagina indice dei prodotti.

E se aggiungiamo .xml all’URL, otteniamo la risposta XML:

La pagina indice dei prodotti come 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:

/app/controller/products_controller.rb

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:

/app/controllers/products_controller.rb

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ì:

/app/controllers/products_controller.rb

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:

Modifica di un prodotto.

Al click del pulsante “Update Product”, viene invocata la action update ed il respond_with eseguirà un redirect alla pagina di quel prodotto:

Il prodotto è stato aggiornato con successo.

Analogamente, se clicchiamo il link “destroy”, saremo ridiretti alla pagina di indice:

respond_with ridirige alla pagina index dopo aver cancellato un prodotto.

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:

/app/controllers/products_controller.rb

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ì:

/app/controllers/products_controller.rb

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:

La pagina di edit che mostra l’XML personalizzato.

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.