Friday, January 8, 2010

Custom Constraints with Grails

While Grails provides a very robust validation mechanism for domain objects, we encountered an issue where the required-ness of certain fields are dependent on the value of another field in the same object.

Enter the grails constraints plugin -- this allows you to create a custom constraint that is reusable in your application.

First install the plugin by executing the command
grails install-plugin constraints


Second, create a groovy file in the grails-app\utils folder with a name ending with 'Constraint.' This file should contain a closure named validate that returns a boolean value.

The closure has three parameters passed to it:
1. the value of the field on which the constraint was placed (val)
2. the instance of the object being validated (target)
3. a collection of validation errors (not used in example)

In addition, it implicitly has access to parameters passed to the constraint via the params property.

class CreditCardNumberRequiredConstraint {
static name = "ccReq"

def validate = { val, target ->
// find the payment method type value on the object
def paymentMethod
if (target.metaClass.hasMetaProperty("paymentMethod")) {
paymentMethod = target.paymentMethod
}

// cc number required, check if the value is populated
if (params.vals.contains(paymentMethod)) {
return val
}
// payment method type does not require cc number
else {
return true
}
}
}


Third, use the new constraint in your domain class. The default name is the camel-cased name of your groovy file, minus Constraint (i.e. - creditCardNumberRequired). This example has over-ridden the default by adding the static name line.
class Payment {
String name
String paymentMethod
String creditCardNumber

static constraints = {
name(nullable:false)
paymentMethod(nullable:false, inList:['AMEX', 'MasterCard', 'Check'])
creditCardNumber(ccReq:[vals:['AMEX', 'Mastercard']])
}
}

Finally, add an entry to the properties files under grails-app\i18n. The key for the entry should be of the format:

default.invalid.% camel-coded-constraint-name %.message

No comments:

Post a Comment