Validazione Parziale in Rails

Capita talvolta di dover implementare la creazione/modifica di un model suddivisa su più di una form.
Un caso abbastanza classico può essere una form di registrazione suddivisa in due step oppure la realizzazione di un wizard guidato composto da diverse pagine.

Questa situazione ci pone davanti ad un problema: come e quando effettuare la validazione dei dati?

La prima idea che può venire in mente è di demandare semplicemente tutte le validazioni all’ultima pagina quando si va realmente a creare/aggiornare il model. Ma questa è anche la soluzione meno elegante, sia dal punto di vista del codice che da quello dell’usabilità.

Cosa fare allora?

Vi presento una tecnica che ho adottato in situazioni simili per realizzare la “validazione parziale di un model”.

L’idea è quella di implementare un metodo che permetta di validare solo gli attributi presenti ad ogni step.

Iniziamo definendo, all’interno del nostro model, un metodo per la validazione di un singolo attributo:

1
2
3
4
5
6
7

def self.valid_attribute?(attrib, value)
mock = self.new(attrib => value)
unless mock.valid?
return !mock.errors.on(attrib).present?
end
true
end

Questo metodo fa uso di un mock per simulare la validazione del model e verifica se nella hash degli errori di validazione sia presente anche l’attributo (attrib) che ci interessa.
Se cosi non fosse il metodo si limita a ritornare true, senza considerare gli altri errori, facendo cosi superare la validazione (parziale!) al model.

Ma vediamone un esempio di utilizzo.
Supponiamo di voler realizzare una form di registrazione in due passaggi, dove nel primo step vengono richiesti utente e password e nel secondo i dati anagrafici.
Aggiungiamo il vincolo che la registrazione sia considerata completa solo se vengono forniti tutti i dati.
In questa situazione ci è comodo utilizzare la validazione parziale.

Definiamo il model User come segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class User < ActiveRecord::Base
validates_presence_of :login, :password, :email, :name, :surname, :street, :city, :country
validates_uniqueness_of :login

#Some code here..

def self.valid_attribute?(attrib, value)
mock = self.new(attrib => value)
unless mock.valid?
return !mock.errors.on(attrib).present?
end
true
end

end

Supponiamo ora di avere una prima form che richiede all’utente di digitare utente, password ed email ed una seconda form che richiede i dati anagrafici.
Le due form eseguono il post sui metodi step1 e step2 del controller UserController.

Nello step1 possiamo ora utilizzare il metodo valid_attribute? definito in precedenza per validare solo gli attributi che effetivamente ci interessano.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

class UserController < ApplicationController

def new
@user = User.new
end

def step1
@user = User.new(params[:user])
if !valid_attribute?(:login, user.login)
or !valid_attribute?(:password, user.password)
or !valid_attribute?(:email, user.email)
#... generate an error message and re-render the form
#...
else
#.. Save user in session and go to step
session[:new_user] = @user
redirect_to :action => "step2"
end
end

def step2
# Get user from session
@user = session[:new_user]
# Update attributes with anagraphic data
@user.update_attributes(params[:user])
if @user.save
flash[:notice] = 'user created'
# Clear Session
session[:new_user] = nil
redirect_to(users_url)
else
render :action => "step2"
end
end

# Other methods...

end

Nello step2 eseguiamo invece il metodo save che fa la validazione “classica” di Rails, poichè è l’ultimo step della nostra registrazione ed è qui che creiamo realmente l’utente.
Con la tecnica mostrata si possono aggiungere quanti step si vuole, con l’accortezza di non salvare mai il model fino a che non si è arrivati all’ultimo step.

Nel caso si debbano realizzare più wizard può essere comodo spostare il metodo validate_attribute? in un module da utilizzare come mixin per tutte le classi che ne hanno bisogno.
Naturalmente questa è solo una possibile soluzione al problema della validazione parziale.

Vi siete mai trovati ad affrontare il problema della validazione parziale?
E se si, come lo avete risolto?