Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create and XOR validation for two fields in a Grails domain class?

I have an issue where my domain class has two potential mutually exclusive external keys, either a serial number or a legacy lookup value.

Since I'm not sure which one I'll have for any given entry, I've made them both nullable and added custom validation to try to ensure I have one and only one value.

package myproject 

class Sample {

    String information
    String legacyLookup
    String serialNumber

    static constraints = {
        information(nullable: true)
        legacyLookup(nullable: true)
        serialNumber(nullable: true)

        legacyLookup validator: {
            return ((serialNumber != null && legacyLookup == null) || (serialNumber == null && legacyLookup != null))
        }

        serialNumber validator: {
            return ((serialNumber != null && legacyLookup == null) || (serialNumber == null && legacyLookup != null))
        }
    }
}

I created the default CRUD screens and tried to create an entry for this domain class

information: Blah Blah
serialNumber: 
legacyLookup: BLAHINDEX123

This dies in the validator with the following message:

No such property: serialNumber for class: myproject.Sample

What am I missing?

like image 767
GeoGriffin Avatar asked Jul 14 '12 04:07

GeoGriffin


1 Answers

Having each property in there multiple times is not necessary; in fact you only need one of them actually constrained. Also you can't just reference properties directly by their name. There are objects that are passed to the constraint closure that are used to get at the values (see the docs). Probably the simplest way I've found to do this is as follows:

class Sample {
    String information
    String legacyLookup
    String serialNumber

    static constraints = {
        information(nullable: true)
        legacyLookup(validator: {val, obj->
            if( (!val && !obj.serialNumber) || (val && obj.serialNumber) ) {
                return 'must.be.one'
            }
        })
    }
}

And then have an entry in the messages.properties file like this:

must.be.one=Please enter either a serial number or a legacy id - not both

Or you could have separate messages for each condition - both are entered, or both are blank like this:

legacyLookup(validator: {val, obj->
    if(!val && !obj.serialNumber) {
         return 'must.be.one'
    }
    if(val && obj.serialNumber) { 
         return 'only.one'
    }
})

And then have two messages in message.properties:

only.one=Don't fill out both
must.be.one=Fill out at least one...

You don't need to return anything from the constraint if there is no error...

like image 131
Kelly Avatar answered Oct 24 '22 09:10

Kelly