Ma cosa fare se l'utente sbaglia ad inserire la password e la verifica fallisce? E se un autore con lo stesso nome esiste già? E se uno dei parametri non viene inserito? I problemi di validazione dell'input in Rails vengono gestiti sempre nel modello. In questo modo, le regole di validazione possono essere riutilizzate da ogni altro elemento dell'applicazione o anche essere riutilizzate in applicazioni differenti.
Per far sì che la validazione sia la più semplice possibile ActiveRecord mette a disposizione differenti metodi speciali, simili a quel metodo scaffold
già visto nei controller. In particolare, potremo cambiare il modello in app/models/author.rb
in questo modo:
class Author < ActiveRecord::Base
validates_presence_of :name, :password
validates_uniqueness_of :name
validates_confirmation_of :password
has_many :messages
end
Cosa significano quelle tre nuove linee? Semplicemente, la prima verifica che gli attributi password
e name
abbiano un valore. La seconda verifica che l'attributo name
non sia già presente nel database, preoccupandosi di effettuare al posto nostro le operazioni in SQL. La terza verifica che password
e password_confirmation
siano uguali. Ancora una volta, ci basta seguire una piccola convenzione, ovvero chiamare i campi nome
e nome_confirmation
per far sì che Rails gestisca tutto automaticamente.
Sebbene esistano molte di queste validazioni predefinite, che permettono ad esempio di verificare il formato o la lunghezza di un campo, di escludere alcuni valori predefiniti e persino di controllare che l'utente abbia accettato delle condizioni (come la classica check box per il trattamento dei dati personali) è comunque sempre possibile definire un metodo apposito, chiamato validate
, che verrà eseguito per verificare regole più complesse, controllando il contenuto dell'oggetto, le sue relazioni ed applicando un controllo di qualunque altro tipo che non corrisponda a quelli disponibili.
Provando a creare degli oggetti di tipo Author
con script/console
possiamo vedere cosa succede quando fallisce una validazione:
>> author=Author.new( :name=>"nome", :password=>"pass", :password_confirmation=>"sbagliata") => #<Author:0x3b8a1b8 @new_record=true, @attributes={"name"=>"nome", "password"=>"pass"}, @password_confirmation="sbagliata"> >> author.save => false
save
restituisce false
invece del solito true
, ad indicare che in effetti l'oggetto non è stato salvato, perché qualcosa è andato male. Gli errori vengono salvati in un attributo speciale degli oggetti del modello, chiamato errors
, e sono accessibili come coppie chiave/valore in questo modo:
>> for name, error in author.errors >> puts name+ ": "+ error >> end password: doesn't match confirmation
Avendo capito come accedere a queste informazioni, ci basterà riutilizzare l'oggetto nella vista, e mostrarne gli errori. Ma ancora una volta, Rails fornisce già un meccanismo molto veloce per farlo, ovvero l'helper error_messages_for
, che può essere usato in questo modo:
<%= error_messages_for "author" %>
Ovviamente, questo produce un codice standard (ed altrettanto standard messaggi di errore) che potrebbero non essere adeguati al nostro sistema, nel qual caso si potrà comunque gestirli da soli con l'approccio già visto (un'iterazione esplicita), o tramite gli altri metodi messi a disposizione dalla classe Errors
. Notate che questo helper utilizza il nome di una variabile, e quindi nel metodo del controller sarà necessario utilizzare una variabile @author.
Notate che quando l'azione viene richiamata tramite un collegamento, cioè tramite GET, l'intero blocco if
non viene eseguito, e quindi la variabile è vuota e nella pagina non viene mostrato nulla, esattamente il comportamento che desideriamo.
La pagina di login sarà sostanzialmente identica, ma in questo caso possiamo rendere la vista ancora più semplice utilizzando un altro helper, chiamato semplicemente form
. Questo helper prende in input il nome di una variabile d'istanza, come error_messages_for
, poi la analizza per vedere quali sono i suoi attributi ed infine costruisce una form appropriata per i suoi campi. Ad esempio, per uno dei nostri oggetti di classe Author
, inserirà un form per la password ed uno per il nome. form
non ha difficoltà a gestire oggetti anche molto complessi, anzi, più il modello è articolato tanto più è utile questo helper, che ci permette di creare una form per una tabella con una mezza dozzina di colonne in una sola riga di codice.
Possiamo inoltre passare degli argomenti aggiuntivi, tra cui le specifiche di quale azione deve essere richiamata dal form, nel nostro caso, specificheremo di nuovo login
. Il codice della vista in app/views/user/login.rhtml potrà essere dunque una cosa del genere:
<h1>Esegue il login</h1>
<%= @invalid_login_error %>
<%= form("author", :action=>'login') %>
Mentre il metodo definito in UserController
, dentro app/controller/user_controller.rb
sarà:
def login
@author=Author.new(params['author'])
if request.post?
if @author.valid_credentials?
logged
else
@invalid_login_error="User o password errati"
end
end
end
abbiamo spostato la creazione dell'oggetto al di fuori dell'if
, in quanto esso ci serve per l'helper form
, in ogni caso.
All'interno del metodo poi, impostiamo una variabile d'errore se l'utente non inserisce dati corretti, e lo redirigiamo verso la home page se invece li ha inseriti correttamente. Il metodo valid_credentials?
invece è un nuovo metodo, che definiremo nel modello per gli autori e cioè nel file app/model/author.rb, in questo modo:
def valid_credentials?
saved=Author.find_by_name(name)
return (saved and (password == saved.password))
end
Ricordate che i vari metodi find
restituiscono nil
se non trovano un oggetto, e che nil
è considerato un valore falso in Ruby.
Ancora una volta, cercate di fare attenzione a dove abbiamo definito le varie funzionalità: capire se un utente è valido o meno è un compito del modello, e quindi abbiamo messo in quel punto la logica. Se un domani decidessimo di offrire la possibilità all'utenti di effettuare operazioni tramite Web service, potremmo riutilizzare questa parte di logica.
Invece, sapere cosa fare con un utente che cerca di effettuare il login via web è compito del controller, e il fatto che questo codice non sia strettamente collegato alla vista fa sì che sia possibile riutilizzarlo ad esempio con una form che si trova in un'altra azione, ad esempio mettendo un box per il login nella homepage.