Reputation: 1046
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
Reputation: 122364
The AST transformation handling @Validateable
augments the class with, among other things
errors
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
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
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