Showing posts with label validation. Show all posts
Showing posts with label validation. Show all posts

Friday, 25 April 2008

Cascading validation

This has confused me for a while. If we have these classes:
class Lolrus {
String name
Bukkit bukkit
}

class Bukkit {
String contents
static constraints = {
contents(validator: {
return 'o.noes'
})
}
}

You will find this test fails because the validate() call does not cascade to the aggregated object:
void testErrorsOnAssociations() {
def lolrus = new Lolrus(name: 'jerome')
lolrus.bukkit = new Bukkit(contents: 'fish')
assert !lolrus.validate()
}

The trick is validate will only cascade in the same circumstances that a save would cascade, so if we add static belongsTo = [lolrus: Lolrus] to the Bukkit class the validation cascades properly and the test works.

Thursday, 24 April 2008

Consistency in custom validators

There are 4 different ways you can get custom validators to attach errors to your domain objects.
  1. Return true/false/null
  2. Return an error code String
  3. Return a List consisting of the error code and args
  4. Attach the errors yourself (only available in the 3 argument validator)
The last of these seems to be mainly useful if you may need to add multiple errors to the field but you lose some of the magic Grails does for you. For example the validators of the following class:
class Lolcat {
String cheezburger
String bukkit
String flavr
static constraints = {
cheezburger(validator: {
return false
})
bukkit(validator: {
return 'epic.fail'
})
flavr(validator: { value, target, errors ->
errors.rejectValue('flavr', 'epic.fail')
})
}
}
will attach the following error codes to an instance's fields when validate is called (the bolded value is the default returned by obj.error.code):
  • cheezburger
    • lolcat.cheezburger.validator.error.Lolcat.cheezburger
    • lolcat.cheezburger.validator.error.cheezburger
    • lolcat.cheezburger.validator.error.java.lang.String
    • lolcat.cheezburger.validator.error
    • lolcat.cheezburger.validator.invalid.Lolcat.cheezburger
    • lolcat.cheezburger.validator.invalid.cheezburger
    • lolcat.cheezburger.validator.invalid.java.lang.String
    • lolcat.cheezburger.validator.invalid
    • validator.invalid.Lolcat.cheezburger
    • validator.invalid.cheezburger
    • validator.invalid.java.lang.String
    • validator.invalid
  • bukkit
    • lolcat.bukkit.validator.error.Lolcat.bukkit
    • lolcat.bukkit.validator.error.bukkit
    • lolcat.bukkit.validator.error.java.lang.String
    • lolcat.bukkit.validator.error
    • lolcat.bukkit.epic.fail.Lolcat.bukkit
    • lolcat.bukkit.epic.fail.bukkit
    • lolcat.bukkit.epic.fail.java.lang.String
    • lolcat.bukkit.epic.fail
    • epic.fail.Lolcat.bukkit
    • epic.fail.bukkit
    • epic.fail.java.lang.String
    • epic.fail
  • flavr
    • epic.fail.Lolcat.flavr
    • epic.fail.flavr
    • epic.fail.java.lang.String
    • epic.fail
You can see that the Grails convention of adding "${class}.${field}." to the front of the error code you return is not happening for the 'flavr' validator.
In addition, on the 'cheezburger' and 'bukkit' validators Grails will automatically attach three arguments:
  1. field name
  2. class name
  3. field value
This is also lost on the 'flavr' validator.
I've created a helper method ValidationUtils.rejectValue(target, errors, fieldName, code, args) that will do all this for you which enables error codes and message arguments to be consistently standardised no matter which type of validator you're writing.