Validation
Validating Domain Classes
Grails allows you to apply constraints to a domain class that can then be used to validate a domain class instance. Constraints are applied using a "constraints" closure which uses the Groovy builder syntax to configure constraints against each property name, for example:
class User {
String login
String password
String email
Date age static constraints = {
login(size:5..15,blank:false,unique:true)
password(size:5..15,blank:false)
email(email:true,blank:false)
age(min:new Date(),nullable:false)
}
}
Note that as of Grails 0.4 your constraints must be static or an exception will be thrown.
To validate a domain class you can call the "validate()" method on any instance:
def user = new User()
// populate propertiesif(user.validate()) {
// do something with user
}
else {
user.errors.allErrors.each {
println it
}
}
The {{errors}} property on domain classes is an instance of the Spring
org.springframework.validation.Errors interface.
By default the persistent "save()" method calls validate before executing hence allowing you to write code like:
if(user.save()) {
return user
}
else {
user.errors.allErrors.each {
println it
}
}
You can also reject domain object values in a controller. You might need to do this if you don't want a new instance of an object to be created if an invalid property or id is passed in as a parameter. For instance:
if(params.networksChosen){
def nlist = new ArrayList();
if(params.networksChosen.class == String){
nlist = params.networksChosen.split(",")
}
else nlist = params.networksChosen;
nlist.each { item->
String cleaned = item.trim()
Network nw = Network.findByNetworkId(cleaned)
if(!nw){
newSnap.errors.rejectValue(
'networks',
'snapshot.networks.notFound',
[cleaned] as Object[],
'Could not locate network: {0}'
)
}
else newSnap.addToNetworks(nw)
}
}In some situations (unusual situations), you might need to know how to transfer an error from a nested child object to a parent domain object. In some circumstances, if you validate the children objects before the parent object, then the errors on the children objects will get reset before the object is sent to the JSP. Here is an example of how to handle nested errors under these circumstances:
class OwnershipSnapshot {
Date dateOfSnapshot
SortedSet sources = new TreeSet() static hasMany = [sources:Source] static constraints = {
dateOfSnapshot()
sources(nullable:true, validator: {val, obj, errors ->
def errorFound = false;
val.each{ src->
if(!src.validate()){
errorFound = true;
src.errors.allErrors.each{error->
obj.errors.rejectValue('sources',
"snapshot.source.invalid",
[src,error.getField(),error.getRejectedValue()] as Object[],
"For source [${src}], field [${error.getField()}] with value [${error.getRejectedValue()}] is invalid.")
}
}
} if(errorFound) return false;
})
}
}For a full reference see the
Validation ReferenceDisplay Errors in the View
So your instance doesn't validate, how do you now display an appropriate error message in the view? For starters you need to redirect to the right action or view with your erroneous bean:
class UserController {
def save = {
def u = new User()
u.properties = params
if(u.save()) {
// do something
}
else {
render(view:'create',model:[user:u])
}
}
}
In this case we use the render method to render the right view, alternatively you could chain the model back to a "create" action:
chain(action:create,model:[user:u])
The chain method stored the model in flash scope so that it is available in the request even after the redirect.
So now to the view, you clearly have an instance with errors, to display them we use a special tag called "
hasErrors":
<g:hasErrors bean="${user}>
<g:renderErrors bean="${user}" as="list" />
</g:hasErrors>
This is used in conjunction with the tag "
renderErrors" which renders the errors as a list. In GSP because you can call tags as regular methods calls it also means you can do some neat tricks to highlight the errors really easily such as:
<div class="prop ${hasErrors(bean:user,field:'login', 'errors')}">
<label for="login"><input type="text" name="login" />
</div>
The above code will add the "errors" CSS class to the property if there are any errors for the field 'login' now simply add a CSS style:
.errors { border: 1px solid red }
And you have the erroneous field highlighting when there is a problem.
Changing the Error Message
Of course the default error message that Grails displays is probably not what you were after, so you will want to change this. The way you do this is by modifying the "grails-app/i18n/messages.properties" file and adding a message for the particular error code.
For example if we follow the above example the error code may be "user.login.length.tooshort" so we add an entry:
user.login.length.tooshort=I'm sorry the login you entered wasn't quite long enough, please make it longer
For a complete list of error codes and how they correspond to validation constraints see the
Validation ReferenceDefining constraints for Hibernate mapped classes
To integrate with the Grails constraints mechanism and hence hook into useful things like the way the views are generated and Grails'
Validation mechanism you can do so by creating a Groovy script following the naming convention of your domain class and ending in the "Constraints" suffix. For example for a "com.books.HibernateBook" class (either an EJB3 entity of mapped with Hibernate XML) defined above you would need to create a "com/books/HibernateBookConstraints.groovy" script in the same package as the class itself, in the src/java directory tree. Within the script just define constraints in the same way as you would do in a GORM class:
/* com.books.HibernateBookConstraints.groovy */
package com.booksconstraints = {
title(size:5..15)
desc(blank:false)
} Note that if there is no a correct package declaration for the constraints class, Grails start-up will loop infinitely.