Recently, I have been working on some code that does not use any of the GORM capabilities in Grails, but instead controllers call to a service layer which interacts with web services hosted on an ESB. The domain classes that are in use are not understood by Grails to be the domain artefact type and as such, do not inherit certain traits. One of the traits that is missing is the ability to cascade validation from the object being validated down to any child objects. Let me illustrate.
Let’s say that we have a class Person which has some properties. One of these is a residential address, which is of type Address.
import grails.validation.Validateable @Validateable class Person { String firstName String lastName Address residenceAddress static constraints = { firstName blank: false //other constraints lastName blank: false //other constraints } } @Validateable class Address { String line1 String line2 String city String state String postalCode static constraints = { line1 blank: false //other constraints city blank: false //other constraints state blank: false //other constraints postalCode blank: false //other constraints } }
Since the Person class is a POGO and not a Grails domain artefact, the first thing we need to do to make this class capable of being validated during Grails controller binding is to mark the class with @Validateable. Then we can add the static constraints block and it will be used to evaluate/validate the properties during binding.
Issues arise when we need to validate the residenceAddress property of Person. How can we accomplish this? One way to do this is to manually validate the properties, and report any errors.
class MyController { // ... def personSubmit(Person p) { // ... // p is already validated, except for residenceAddress def anyErrors = p.hasErrors() || !p.residenceAddress.validate() if(anyErrors) { // report errors ... } } }
The problem with the above approach is two-fold. First, it doesn’t scale. We shouldn’t be validating the parent object and then again validating a child property. Imagine if this class had multiple child properties that were themselves object that again contained properties that were objects. What a nightmare to validate. Second, since you are manually invoking the validate method out of sequence with what is in the constraints block, any errors are reported back within their own object and there is no way to know what the original sequence was for the errors. This is particularly annoying when you need to display errors to a user that were part of a form submission and they are listed out in an order inconsistent with the layout of the form.
So, I decided that I would write some code that would would provide the functionality that is missing with POGOs.
import org.codehaus.groovy.grails.validation.* import org.springframework.validation.* class CascadeValidationConstraint extends AbstractVetoingConstraint { public static final String NAME = "cascadeValidation" @Override String getName() { NAME } @Override boolean supports(Class type) { true } @Override public void setParameter(Object constraintParameter) { if (!(constraintParameter instanceof Boolean)) { throw new IllegalArgumentException( """Parameter for constraint [$name] of property [$constraintPropertyName] of class [$constraintOwningClass] must be a Boolean """ ) } super.setParameter(constraintParameter) } @Override protected boolean skipNullValues() { return true } @Override protected boolean processValidateWithVetoing( Object target, Object propertyValue, Errors errors) { if (!propertyValue.validate()) { propertyValue.errors.fieldErrors.each { String field = "${propertyName}.${it.field}" def fieldError = new FieldError( target.errors.objectName, field, it.rejectedValue, it.bindingFailure, it.codes, it.arguments, it.defaultMessage ) errors.addError(fieldError) } return false } return true } }
What the above constraint does is validate the given property, and if it doesn’t pass validation according to the constraints block defined on that class, adds each error as a FieldError to the property’s parent object at the correct field location. Then you just need to register your custom constraint. You can do this in Config.groovy, Bootstrap.groovy, or during plug-in initialization if you build this into a plug-in.
import org.codehaus.groovy.grails.validation ConstrainedProperty.registerNewConstraint( CascadeValidationConstraint.NAME, CascadeValidationConstraint.class )
Now our Person class can be defined as follows and all validations are handled as expected (note the highlighted line below).
import grails.validation.Validateable @Validateable class Person { String firstName String lastName Address residenceAddress static constraints = { firstName blank: false //other constraints lastName blank: false //other constraints residenceAddress cascadeValidation: true } } @Validateable class Address { String line1 String line2 String city String state String postalCode static constraints = { line1 blank: false //other constraints city blank: false //other constraints state blank: false //other constraints postalCode blank: false //other constraints } }
neat !
Eric,
I would like to publish a small Grails plugin based on your code, with a few minor changes (ex: I’ve updated the supports() method to check whether a validate() method exists on the target type). I’ve credited you in the source code and the plugin description. Let me know if it’s OK to use your code and I will put in a request to add it to the plugin repo.
Thanks
Russell,
That is completely fine with me. Let me know the plugin once you publish it; I will try it out!
Thanks
Eric,
Thanks! I finally got around to making the plugin public:
https://grails.org/plugins/pending/458
https://github.com/rmorrise/
Let me know if you see any issues!
The plugin was approved! Here’s the updated URL:
http://grails.org/plugin/cascade-validation
FYI… I discovered when trying to implement a different custom constraint that the constraint may not get attached to Hibernate-enabled domain objects when registered in BootStrap.groovy. It looks like BootStrap.groovy is too late in the config process; the list of constraints on each property is already built up by the time BootStrap.groovy runs. I moved it to Config.groovy and that fixed the issue.
Thanks for the update! On the Bootstrap vs Config setting, I didn’t run into that issue because on the project where I used it, it didn’t use Hibernate domain classes. I will keep note of that for the future.
I am almost a newbie to Grails. Is there a way I can use this for a list of Command objects inside the parent. As shown below Person has a list of Account and how can i set cascadeValidation property to this.
class Person {
String firstName
String lastName
Address residenceAddress
List bankAccounts
static constraints = {
firstName blank: false //other constraints
lastName blank: false //other constraints
}
}
Prasanna, there is now a public version of the plugin available. It has the capability to also cascade when the child object is a collection.
I know it may be a while, but I think there’s a mispelling when registering the custom validator.
import org.codehaus.groovy.grails.validation
ConstrainedProperty.registerNewConstraint(
*CascadeValidationConstraint*.NAME,
*CascadeValidationConstraint*.class
)
The plugin is hella cool, congrats!!
Thanks for the catch, almost 2 years later!
I have updated the post to fix the misspellings.