views:

1147

answers:

2

Hi all,

This one is for the Grails users here. I asked it on the grails - user mailing list, but I figured since I've been fighting this for a couple of days I should cast as wide a net as possible.

I'm having some difficulty with trying to model relationships between two objects of the same type in another object (different type) referencing the two objects.

As an example of what I'm trying to do, suppose you're modeling relationships among family members. Any given relationship "belongsTo" two different family members. So:

class Person {
   hasMany[relationships: Relationship]

   static mappedBy = [relationships:'p1', relationships:'p2']
}

class Relationship {

   Person p1
   Person p2
   String natureOfRelationship // for example, "cousins"

   static belongsTo = [p1: Person, p2: Person]
}

The intent here is that if either p1 or p2 is deleted, then the delete will cascade to all Relationship objects in the hasMany map. Instead, every time I try it, I end up with a foreign key violation. I tried using the "cascade" attribute as covered in the documentation:

http://grails.org/doc/1.0.x/guide/single.html#5.5.2.9%20Custom%20Cascade%20Behaviour

So I figured I would add this to the Person class:

static mapping = {
    relationships cascade:'delete'
}

I didn't have any luck with that either.

I also looked at the devDB.script file that Grails generates, to see how it was setting up the foreign keys on Relationship. If I manually add "ON DELETE CASCADE" to both foreign key constraints, then it works fine, but obviously doing manual edits to an automatically generated database script is not the most robust solution. Ideally I'd like to be able to specify that behavior using GORM.

So what's my best bet here? Is there a way to force cascading deletes on multiple foreign keys/owners? Would I need to do this manually with an onDelete action on Person? Do I need to get into Hibernate configs for this, or can I do it in Grails/GORM some way?

Thanks very much for your time and for any assistance you can offer.

+1  A: 

You can add a beforeDelete hook to the Person class and query the other parent. If the other parent does not exist, you can delete the relationship. Note that you are hitting foreign key violations because you probably need to delete both parents, since the relationship has an FK to both of them.

Miguel Ping
A: 

You can also define 2 Relationshipcollections in Person

incomingRelations and outgoingRelations seem usable words to distinguish (if applicable to your domain).

You could define a transient property relationships with a getter only, which returns the union of both relationshipcollections (an immutable to be sure to not modify it / those changes would most likely make no sense)

class Person {
   Relationship incomingRelations
   Relationship outgoingRelations
   static mappedBy = [incomingRelations:'p1', outgoingRelations:'p2']

   static transients = ['relations']

   Set getRelations() {
       //creates a new collection as union of the old ones
       return Collections.unmodifiableSet(this.incomingRelations + this.outgoingRelations)
   }
}
class Relationship {
    static belongsTo = [p1:Person, p2:Person]
}

if doesn't fit i would try the even approach suggested by Miguel-Ping

squiddle