2012-07-14 8 views
5

Ho un problema in cui la mia classe di dominio ha due potenziali chiavi esterne reciprocamente esclusive, un numero di serie o un valore di ricerca legacy.Come posso creare e convalida XOR per due campi in una classe di dominio Grails?

Dal momento che non sono sicuro di quale avrò per una determinata voce, li ho resi nullable e ho aggiunto la convalida personalizzata per provare ad avere un solo e unico valore.

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)) 
     } 
    } 
} 

ho creato gli schermi di default CRUD e ha cercato di creare una voce per questa classe di dominio

information: Blah Blah 
serialNumber: 
legacyLookup: BLAHINDEX123 

Questo muore nel validatore con il seguente messaggio:

No such property: serialNumber for class: myproject.Sample 

Che cosa sono io mancante?

risposta

9

Non è necessario avere più di una proprietà in quella sede; in realtà hai solo bisogno di uno di loro effettivamente limitato. Inoltre non puoi semplicemente fare riferimento alle proprietà direttamente con il loro nome. Esistono oggetti che vengono passati alla chiusura del vincolo utilizzata per ottenere i valori (vedere docs). Probabilmente il modo più semplice che ho trovato per fare questo è la seguente:

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' 
      } 
     }) 
    } 
} 

e quindi avere una voce nel messages.properties file in questo modo:

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

Oppure si potrebbe avere messaggi separati per ogni condizione - entrambi sono entrati, o entrambi sono vuoti come questo:

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

e poi hanno due messaggi in message.properties:

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

Non è necessario tornare nulla dal vincolo se non ci sono errori ...

+0

Questo ha fatto esattamente quello di cui avevo bisogno. Stavo pensando che avevo bisogno di validazione in modo esplicito su entrambi i campi, ma uno gestisce l'altro. Grazie! – GeoGriffin

0

Se si desidera assicurarsi di disporre di "un solo valore", un'altra opzione potrebbe essere quella di creare un campo generico denominato serialNumberLegacyLookup che rappresenterebbe entrambi i campi serialNumber e legacyLookup. Quindi è possibile aggiungere un campo booleano alla classe di dominio denominata legacyLookup, che per impostazione predefinita sarà false. È quindi possibile eseguire il valore attraverso il validatore e analizzarlo per vedere se si tratta di un valore "numero seriale" o "legacy lookup". Se il valore risulta essere un valore di "legacy lookup", impostare il valore booleano legacyLookup su true. Penso che questo approccio sarebbe meno confuso da una prospettiva di interfaccia utente (solo un campo per l'utente da compilare invece di due campi condizionali).

+0

Poiché il codice con cui sto interagendo interagisce con sistemi legacy esterni, non penso che questa soluzione sia appropriata. I dati che vengono da me hanno un campo o l'altro, ma sono modellati come campi unici. Potrebbero esserci alcune regole commerciali che a me sono sconosciute in questo momento e potrebbero compromettere la mia comprensione del modello. Per isolarmi da questa possibilità, li terrò separati. – GeoGriffin

0

mi trovai di fronte a questo stesso scenario e la soluzione che ho trovato è stato quello di creare un metodo getter e aggiungere un vincolo ad esso.

package myproject 

class Sample { 

    String information 
    String legacyLookup 
    String serialNumber 

    def getTarget(){ 
     if (legacyLookup && !serialNumber) { 
      return legacyLookup 
     } else if (!legacyLookup && serialNumber) { 
      return serialNumber 
     } else { 
      return null 
     } 
    } 

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