Partial Validation in Rails

It happens sometimes that you have to implement creation or editing of a model splitted on more than one form.
A classic case is a registration form divided into two steps or a guided wizard with several pages.

This situation presents us with a problem: when and how to validate data?

The first idea that may come to mind is simply to leave it all validations in the last page when you go to create or update the model. But this is also the least elegant solutions, both in terms of code than usability.

What to do?

I present a technique that I adopted in similar circumstances to achieve “partial validation of a model”.

The idea is to implement a method that validates only the attributes present at every step.

We begin by defining, within our model, a method to validate a single attribute:

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

This method uses of a mock object to simulate the validation of model and after it verifies whether the hash of validation errors contains the attribute (attrib) that interests us.
If not, the method simply return true, regardless of other errors, thus overcoming the validation (partial!) of the model.

But let’s see an example.
Suppose we want to create a registration form in two steps, where in the first step login and password are required and in the second one anagraphic data are required.
We also add the constraint that registration is to be considered complete only when all data are provided.
In such a situation it is convenient to use partial validation.

We go to define the user model as follows:

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

Now suppose we have a first form that requires users to enter login, password and email and a second form that asks for personal information.
The two forms call respectively the methods step1 and step2 of the UserController.

In the step1 we can now use the valid_attribute? method defined above to validate only the attributes that interest us.

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

In the step2 we execute instead the save method the runs the “classic” Rails validation, cause it is the last step of our registration wizard and is here where you actually creates the user.
With the technique shown you can add as many steps you want, with the care of not ever save the model until it has reached the last step.

If you need to make more wizards inside your application, may be convenient to move the validate_attribute? method in a module to be used as mixin classes where you need it.
Obviously this is only one possible solution to the partial validation problem.

Have you ever faced the partial validation problem?
And if so, how did you solved it?