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
- around_filter
- after_filter
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 # GET /notes # GET /notes.xml
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 # richiedo autenticazione HTTP confrontando nome utente e password # con quelli specificati nel database
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 # filtro con il solo metodo check_note_authentication da attivare # solo per l'action 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.