Secondo il pattern MVC seguito da Rails, il controller ha il compito di rispondere alle richieste del client utilizzando i modelli per interagire con i dati e le viste per interagire con gli stessi client.
Utilizzando i filtri, Rails permette di definire una catena di metodi da eseguire prima o dopo l'esecuzione delle action di un controller; l'utilizzo dei filtri risulta particolarmente utile quando una stessa porzione di codice deve essere eseguita in action differenti.
I filtri sono dichiarati all'interno di un controller e coinvolgono esclusivamente le action di quel controller; se i filtri interessano le action di controller differenti all'interno della stessa applicazione allora sono definiti nel controller ApplicationController
.
Esistono tre tipi di filtri:
- before_filter - definisce il codice eseguire prima di una action
- around_filter - definisce il codice da eseguire prima e dopo una action
- after_filter - definisce il codice eseguire dopo una action
L'ordine di esecuzione nel caso in cui tutti e tre i tipi di filtri siano definiti per una stessa action è la seguente:
- before_filter
- around_filter
- action
- around_filter
- after_filter
Quando si dichiara un filtro è possibile specificare una catena di metodi da eseguire; ad esempio:
before_filter [:metodo_uno, : metodo_due]
oppure un block contenente direttamente il codice:
before_filter do |controller, action| ... end
Per illustrare il funzionamento dei filtri utilizziamo un'applicazione di esempio. Supponiamo di voler gestire delle note riservate, consultabili solo dopo l'autenticazione con nome utente e password differenti per ogni nota. Una volta introdotte le credenziali per una nota, questa viene visualizzata a schermo e quindi subito cancellata dal database.
L'applicazione deve inoltre consentire ad un amministratore, con nome utente e password propri, di creare nuove note e gestire quelle già esistenti.
Creiamo una nuova applicazione e la risorsa Note
con tutte le action necessarie per la sua gestione. Utilizziamo lo script scaffold
per generare automaticamente routing, modello, pagine e controller.
rails notes cd notes ruby script/generate scaffold Note title:string content:text username:string password:string rake db:migrate
I campi username
e password
saranno conservati in chiaro nome utente e password da introdurre per la visualizzazione della singola nota. Dal punto di vista della sicurezza questa non è una buona soluzione, ma ci permette di indagare il funzionamento dei filtri senza introdurre altri concetti di disturbo.
before_filter
Puntiamo il browser all'indirizzo http://localhost:3000/notes
per controllare che tutte le funzionalità base di gestione della risorsa Note (creazione, modifica, visualizzazione, cancellazione) siano presenti. Per proteggere con autenticazione le action che abbiamo appena creato ricorreremo a authenticate_or_request_with_http_basic
del modulo ActionController::HttpAuthentication::Basic
per definire una semplice funzione di autenticazione HTTP con username e password fissi per l'amministratore; poiché non abbiamo intenzione di rendere questo nuovo metodo accessibile direttamente al client, aggiungiamo il codice in fondo al controller, preceduto dalla parola chiave private
:
file: app/controllers/notes_controller.rb ... def destroy @note = Note.find(params[:id]) @note.destroy respond_to do |format| format.html { redirect_to(notes_url) } format.xml { head :ok } end end private def check_authentication authenticate_or_request_with_http_basic do |username, password| username == "nome" && password == "parola" end end end
Il metodo check_authentication
esegue un'autenticazione HTTP riconoscendo come validi solo il nome utente e la password specificati nel codice; se le credenziali non sono inserite correttamente restituisce il codice HTTP 401 Unauthorized
e blocca l'esecuzione della richiesta. Volendo richiedere l'inserimento delle credenziali di autenticazione per tutte le action del controller definiamo il seguente filtro:
class NotesController < ApplicationController # filtro con il solo metodo check_authentication da eseguire # prima di ogni azione del controller before_filter :check_authentication # GET /notes # GET /notes.xml def index ...
Avviamo il server dal prompt dei comandi con ruby script/server
e osserviamo come accedendo a http://localhost:3000/notes
venga richiesta l'immissione delle credenziali di autenticazione. Se si inseriscono credenziali sbagliate, si osserva, dal log stampato a prompt, come il metodo check_authentication
abbia bloccato l'esecuzione della richiesta restituendo il codice HTTP 401
.
Processing NotesController#index (for 127.0.0.1 at 2008-11-23 19:27:49) [GET] Filter chain halted as [:check_authentication] rendered_or_redirected. Completed in 1ms (View: 0, DB: 0) | 401 Unauthorized [http://localhost/notes]
Una volta autenticati è possibile accedere a tutte le action del controller indistintamente, fino a che la sessione del browser non sarà distrutta chiudendo e riaprendo il browser.
Ora occupiamoci dell'action show
, che permette di visualizzare una singola nota. Per questa action vogliamo che il client si autentichi inserendo il nome utente e la password relativa alla singola nota salvati nel database; il metodo di autenticazione sarà quindi:
def check_note_authentication # estraggo l'istanza di note dal database @note = Note.find(params[:id]) # richiedo autenticazione HTTP confrontando nome utente e password # con quelli specificati nel database authenticate_or_request_with_http_basic do |username, password| username == @note.username && password == @note.password end end
Inserito il metodo subito sotto il metodo check_authentication
, così che risulti anche questo un metodo privato non accessibile direttamente dal client, raffiniamo la dichiarazione del filtro così da attivare check_authentication
per tutte le action tranne che per show
, ed attivare check_note_authentication
solo per questa action:
# filtro con il solo metodo check_authentication da eseguire # prima di ogni azione del controller tranne show before_filter :check_authentication, :except => :show # filtro con il solo metodo check_note_authentication da attivare # solo per l'action show before_filter :check_note_authentication, :only => :show
Consultando la lista delle note (http://localhost:3000/notes
), viene richiesta l'autenticazione con le credenziali inserite nel metodo check_note_authentication
, quelle dell'amministratore; selezionando il link show
corrispondente ad una delle note viene invece richiesta l'immissione delle credenziali definite per quella nota, e il testo viene visualizzato solo se si immettono le credenziali corrette.
after_filter
Una volta visualizzato il testo della nota, vogliamo che questa venga cancellata dal database. Anche se questa operazione potrebbe essere inserita normalmente nel codice della action show
, procederemo a definire un metodo da eseguire tramite filtro ogni volta che l'action show
è terminata. Analogamente al caso precedente definiamo il metodo privato delete_read_note
, posizionandolo sotto il metodo check_note_authentication
:
def delete_read_note @note.delete end
Inseriamo nella catena dei metodi da eseguire dopo l'esecuzione della action show il metodo appena definito:
... before_filter :check_note_authentication, :only => :show after_filter :delete_read_note, :only => :show ...
Consultando una delle note precedentemente introdotte nel database e tornado alla lista delle note notiamo come le note visualizzate non siano più presenti.
È interessante osservare come all'interno del metodo delete_read_note
si sia utilizzata la variabile @note
, definita all'interno della action show
; le variabili sono quindi condivise fra i filtri e le relative action.
around_filter
Definiamo come ultimo un metodo contenente codice da eseguire prima e dopo l'esecuzione di ogni action:
... around_filter :print_something ... def print_something puts "Prima della action" yield puts "Dopo la action" end end
La action viene eseguita in corrispondenza di yield
; tutto quello che appare prima e dopo yield
viene eseguito rispettivamente prima e dopo la action. In assenza di uno yield
all'interno del codice del metodo la action non viene eseguita e il flusso dell'applicazione Rails interrotto.