When Type Systems Attack

Groovy’s type system can sometimes have funny interplay and unexpected results. For example, these assertions are true:

However, mixing in a Map into this causes some complications:

This might not seem like a big issue, but it can cause some real headaches. When failing an assertion the message printed only prints values, not types. Because of this you may occasionally run into less than helpful error messages like:

And this can be awfully confusing. One prime example of this is with GORM domains. GORM defaults primary keys to be Longs so if you use a primary key as a map key you could run into some pretty confusing error cases that your tests don’t explain as helpfully as you’d like.

Grails/DatabaseMigrationPlugin/Liquibase Quirk

I just fought with this for a while and figured I'd record this in the vast internets in case others come across this. While attempting to extend the functionality of our Grails project at work I needed to add a new table and related foreign key constraints. We use the Grails Database Migration Plugin which uses liquibase to keep our schema in sync between production, staging, and development boxes. I declared a foreign key constraint similar to this one:

and got this exception:

It turns out that passing in a named parameter of referencesUniqueColumn with a value of anything other than false causes this exception. This wasn't immediately obvious to me and I still don't know why, but I thought I'd note it in case someone else ran into it too.

Grails Custom Validation messages

Grails' GORM provides convenient ways to define many different constraints for inputing properties including basic ones like max, equal, & notEqual to more specific ones like creditCard, url, & email (full list here under Quick Reference). But sooner or later you’ll want to create your own custom validator. This can be done by providing a validator closure to your domain, for example:

Now, in this example a domain will validate all of the fields, including the user’s favorite prime (to a degree of certainty). However, if the prime number certainly isn’t prime then the error message provided when validation fails (through validate or save) will be:

favoritePrime does not pass custom validation.

That probably isn’t a helpful message to eventually pass back to the user, they might guess what the mysterious “custom validation” is in this case, but certainly not in all others.

So what is the solution? You might try passing an error message back, but that won’t work; you’ll get the same message again. It turns out the proper approach is to return a key from your message.properties file. If the key exists in the proper localization file (e.g. French is messages_fr.properties) then it will use that string. A simple modification to the validator above will solve this:

Adding the line:

my.localized.not.prime.message=The number {2} is not prime.

Will cause Grails to generate a much more useful validation failed message. As you can probably guess the {2} will be replaced with the value inputed by the user.

You can include additional values to your message by returning an array. The first element should be a message key (e.g. ‘my.localized.not.prime.message’) and the subsequent entries will be interpolated into the message starting at index 3. So, if you returned ['my.localized.not.prime.message', 5] you could have the message: my.localized.not.prime.message=The number {2} is not prime, {3} is an example of a prime. And in this case if the user passed in 4 they would end up with the message:

The number 4 is not prime, 5 is an example of a prime.

Of course, if you aren’t using a prime number validator multiple times in your code it may seem silly to have a special key/value pair dedicated to primes. Another option is to use a key/value pair dedicated to that field. You can do this simply by using the code in the first example and addding a field to your message.properties file like:

Groovyist.favoritePrime.validator.invalid=The number {2} is not prime.

Much of this is documented in the validator docs.