There are already a couple of good tutorials on the web about creating custom grails constraints, but they include some extra cruft that you don't really need in their example classes. All you really need for your custom constraint class is to override the processValidate()
method of the AbstractConstraint
class to run your custom validation, and call rejectValue()
if it fails:
package myapp
import org.codehaus.groovy.grails.validation.AbstractConstraint
import org.springframework.validation.Errors
class LowerCaseConstraint extends AbstractConstraint {
static NAME = 'lowerCase'
boolean supports(Class type) {
true
}
String getName() {
NAME
}
protected void processValidate(Object target, Object value, Errors errors) {
if (constraintParameter && value =~ /[A-Z]/)
rejectValue target, errors, "default.invalid.${name}.message", "${name}.invalid",
[constraintPropertyName, constraintOwningClass, value] as Object[]
}
}
The above example checks if the constraintParameter
property is true (or at least truthy), and if so, rejects the value if it contains any uppercase ascii chars. The constraintParameter
property is the value specified for this constraint in the domain class's constraints
DSL. In the following example, it's true
(from the lowerCase: true
part):
static constraints = {
myProperty size: 1..20, lowerCase: true
}
The other thing you need to do is register your custom constraint; do this by adding the following line to your conf/Config.groovy
(wrapped to fit the content area of this blog):
org.codehaus.groovy.grails.validation.
ConstrainedProperty.registerNewConstraint myapp.LowerCaseConstraint.NAME,
myapp.LowerCaseConstraint.class
And you're probably better off overriding and implementing a few more of the AbstractConstraint
methods just to make sure that the constraint is used correctly (applied to an appropriate property type, and passed an appropriate constraint parameter). Also, it's probably better to segregate your validation logic into a separate method, for clarity and to make it easier to unit test:
package myapp
import org.codehaus.groovy.grails.validation.AbstractConstraint
import org.springframework.validation.Errors
class LowerCaseConstraint extends AbstractConstraint {
static NAME = 'lowerCase'
// overridden Constraint methods
/** Returns true if this constraint can be applied
* to a domain property of the specified type. */
boolean supports(Class type) {
type != null && String.class.isAssignableFrom(type)
}
/** Sets the constraintParameter value
* (first checking if the constraint parameter is of the correct type). */
void setParameter(Object param) {
if (!(param instanceof Boolean))
throw new IllegalArgumentException("Parameter for constraint [$name] of property [$constraintPropertyName] of class [$constraintOwningClass] must be a boolean")
super.setParameter(param)
}
/** Returns the name of this constraint. */
String getName() {
NAME
}
/** Adds an error to the specified errors object
* if the specified property-value does not conform to this constraint
* for the specified target object. */
protected void processValidate(Object target, Object value, Errors errors) {
if (constraintParameter && !validate(target, value))
rejectValue target, errors, "default.invalid.${name}.message", "${name}.invalid",
[constraintPropertyName, constraintOwningClass, value] as Object[]
}
// custom implementation methods
/** Returns true if the specified value conforms to this constraint. */
def validate(target, value) {
!(value =~ /[A-Z]/)
}
}
You then can easily write a unit test for your constraint:
package myapp
import grails.test.GrailsUnitTestCase
class LowerCaseConstraintTests extends GrailsUnitTestCase {
def constraint
protected void setUp() {
super.setUp()
constraint = new LowerCaseConstraint()
}
/** Asserts that the specified string passes this constraint. */
protected validateTrue(s) {
assertTrue constraint.validate(null, s)
}
/** Asserts that the specified string fails this constraint. */
protected validateFalse(s) {
assertFalse constraint.validate(null, s)
}
void testPassesWhenValueIsNull() {
validateTrue null
}
void testPassesWhenValueIsEmpty() {
validateTrue ''
}
void testPassesWhenValueIsLowerCaseString() {
validateTrue 'foobar'
}
void testPassesWhenValueIsStringOfNumbers() {
validateTrue '1234'
}
void testFailsWhenValueIsUpperCaseString() {
validateFalse 'FOOBAR'
}
void testFailsWhenValueIsMixedCaseString() {
validateFalse 'fooBar'
}
}
For a five-star tutorial on how to build your custom constraint as a plugin, check out Geoff Lane's Build a Custom Validator in Grails with a Plugin blog post.
Thank you, this was most helpful to me! I appreciate the fact that you included everything you need to know in the post and didn't leave some things to be filled in.
ReplyDelete