hibernategrailsgrails-domain-classgrails-controllergrails-validation

Grails validation over own domain


I'm using Grails 2.4.2 and have a class Contract which has many InvoiceRecipient's. The InvoiceRecipients class has an attribute invoiceType which has 2 possible values, 'O' for the invoice-original and 'C' for an invoice-copy. As you could imagine, only one record with type 'O' is allowed for the InvoiceRecipients for one contract.

If I try to implement it as in the following snipplet, the VM runs into a StackOverflow.

Another approach I tried was a service method which iterates through the recipients array of the contract to count the records with invoiceType 'O' or I tried to do a select count through InvoiceRecipient.countByContractAndInvoiceType() to determine the number of 'O's in the contract->invoiceRecipients relation in the controller.

In both last cases, Hibernate generates an update statement for my current InvoiceRecipient record, which I try to validate. And even if the validation of the current InvoiceRecipient fails and I populate the errors-object of the instance, the record is already updated (without problems, because the constraint is not coded into the class and throws no error in "save".) And I have the logical-wrong records in the database.

class Contract implements Serializable {
    ...
    static hasMany = [recipients: InvoiceRecipient]
    ...
}

class InvoiceRecipient implements Serializable {
    static belongsTo = [contract: Contract]
    ...
    String invoiceType
    ...

    static constraints = {
        invoiceType nullable: false, maxLength: 1, inList: ['O', 'C'], validator: { val, obj ->
        /* => This generates a StackOverflow
        if (InvoiceRecipient.countByContractAndInvoiceType(obj.contract, 'O') > 1)
            return "invoiceRecipient.original.max.exceed"
        */
    }
}

Solution

  • I'd probably use something like this:

    validator: { val, obj ->
    if (obj.recipients.findAll{invoiceType == 'O'}.size() > 1)
            return "invoiceRecipient.original.max.exceed"
    

    This way you should be able to prevent Hibernate from trying to flush dirty objects and in the process revalidate this object.