Jim Wallace
Jim Wallace

Reputation: 1046

Where does Grail's errors property come from?

Grails has a bug with regards to databinding in that it throws a cast exception when you're dealing with bad numerical input. JIRA: http://jira.grails.org/browse/GRAILS-6766

To fix this I've written the following code to manually handle the numerical input on the POGO class Foo located in src/groovy

void setPrice(String priceStr)
{
    this.priceString = priceStr

    // Remove $ and , 
    priceStr = priceStr.trim().replaceAll(java.util.regex.Matcher.quoteReplacement('$'),'').replaceAll(',','')

    if (!priceStr.isDouble()) {
        errors.reject(
            'trade.price.invalidformat',
            [priceString] as Object[],
            'Price:[{0}] is an invalid price.')

        errors.rejectValue(
            'price',
            'trade.price.invalidformat')
    } else {
        this.price = priceStr.toDouble();
    }
}

The following throws a null reference exception on the errors.reject() line.

foo.price = "asdf" // throws null reference on errors.reject()
foo.validate()

However, I can say:

foo.validate()
foo.price = "asdf" // no Null exception
foo.hasErrors() // false
foo.validate()
foo.hasErrors() // true

Where does errors come from when validate() is called? Is there a way to add the errors property without calling validate() first?

Upvotes: 4

Views: 1351

Answers (3)

Ian Roberts
Ian Roberts

Reputation: 122364

The AST transformation handling @Validateable augments the class with, among other things

  • a field named errors
  • public methods getErrors, setErrors, clearErrors and hasErrors

The getErrors method lazily sets the errors field if it hasn't yet been set. So it looks like what's happening is that accesses to errors within the same class are treated as field accesses rather than Java Bean property accesses, and bypassing the lazy initialization.

So the fix appears to be to use getErrors() instead of just errors.

Upvotes: 5

doelleri
doelleri

Reputation: 19682

I can't exactly tell you why, but you need to call getErrors() explicitly instead of accessing it as errors like a property. For some reason, Groovy isn't calling the method for it. So change the reject lines in setPrice() to

getErrors().reject(
        'trade.price.invalidformat',
        [priceString] as Object[],
        'Price:[{0}] is an invalid price.')

getErrors().rejectValue(
        'price',
        'trade.price.invalidformat')

That is the easiest way to make sure the Errors object exists in your method. You can check out the code that adds the validation related methods to your domain class.

Upvotes: 6

user800014
user800014

Reputation:

The errors are add to your validateable classes (domain classes and classes that have the annotation @Validateable) dinamically.

Allowing the developer to set a String instead of a number doesn't seem a good way to go. Also, your validation will work only for that particular class.

I think that a better approach is to register a custom property editor for numbers. Here's a example with dates, that enable the transform of String (comming from the form) to Date with a format like dd/MM/yyyy. The idea is the same, as you will enforce that your number is parseable (eg. Integer.parseInt() will throw exception).

In your domain class, use the numeric type instead of String, so by code developers will not be allowed to store not number values.

Upvotes: 2

Related Questions