Search code examples
grailsforeign-key-relationship

grails foreign key error in delete parent row


I have the following situation:

class Receipt {

   BigDecimal totalAmount;
   Date releaseDate;

   @NotNull
   Integer vatPercentage;

   @NotNull
   Integer discount;

   Boolean isPayed;
   Boolean isInvoice;
   Boolean hasStampDuty;

   Integer documentNumber;

   @NotNull
   static belongsTo = [patient:Patient, doctor:Doctor]

   @NotNull
   static hasMany = [healthServices:Receipt_HealthService]


    static constraints = {
        healthServices(blank:  false)
        patient(blank: false)
        totalAmount(blank: false, )
        vatPercentage(blank: false, nullable: false)

    }


}


class HealthService {

   int vat;
   String description;
   BigDecimal price;

   static belongsTo = [healthServiceType:HealthServiceType, doctor:Doctor]

   static constraints = {
      healthServiceType(blank: false)
      vat(size: 11..11)
      description(maxSize: 255)

    }
}  


class Receipt_HealthService {

   Receipt receipt
   HealthService healthService
   int quantity = 1

   static constraints = {
   }
}

I've manually created the Receipt_HealthService domain class, as described in the post here. Everything works well, but when I try to delete a Receipt instance, I see the following error:

Cannot delete or update a parent row: a foreign key constraint fails
(`my_localdb`.`receipt_health_service`, CONSTRAINT 
`FK96DE98B9D3292D2C` FOREIGN KEY (`receipt_id`) REFERENCES `receipt`(`id`))

Why do this happen? Why the Receipt_HealthService instances are not automatically deleted? What do I need to change to allow the automatic deletion and remove the error?


Solution

  • The class Receipt_HealthService doesn't have a belongsTo, therefore Receipt's deletions are not cascaded. See http://grails.github.io/grails-doc/latest/guide/GORM.html#cascades .

    You can either change Receipt_HealthService as following:

    class Receipt_HealthService {
       ...
       static belongsTo = [receipt: Receipt]
    }
    

    Or try to define the cascade behaviour explicitely (see http://grails.github.io/grails-doc/latest/guide/GORM.html#customCascadeBehaviour).

    Another posibility is to add a beforeDelete to Receipt that takes care of deleting each entry in healthServices. See http://grails.github.io/grails-doc/latest/guide/GORM.html#eventsAutoTimestamping

    For this last option, you can check the classes User, UserRole and Role from the Spring Security Plugin as an example. I didn't find a sample project online, but you can just install the plugin in a sample project and run grails s2-quickstart to see how the User#beforeDelete looks like (see https://grails-plugins.github.io/grails-spring-security-core/ref/Scripts/s2-quickstart.html).