[PEAK] Cascading delete

Phillip J. Eby pje at telecommunity.com
Mon Jul 25 11:15:22 EDT 2005

At 02:52 PM 7/25/2005 +0000, Tiago Cogumbreiro wrote:
> > >Is calling the event _after_ the super.remove a good thing to do?
> >
> > The reason I did that is so that the super method will have a chance to
> > make sure that it's a valid thing to remove.  Perhaps the code should first
> > get the object's _p_oid and send that as the value of the removed event,
> > though.
>Do you mean something like:
>oid = obj._p_oid
>super(WhateverDM, self).remove (obj)
>self.removed.send ((obj, oid))


> > Note that an object that has just been added may have no oid, yet you still
> > may need to break references to it in the foreign key DM to avoid
> > "resurrecting" it.  This is why I was having the event send the object,
> > rather than the oid.
>I am assuming that these type of objects are the ones that are created
>and removed in the same transaction. Do they need to be affected by
>the cascading delete?
>That's another reason why I decided to use "_delete_oids". Objects are
>didn't reach the database (aka called _new) are simply discarded.

Well, if you have the luxury of doing that in your application, 
sure.  However, you might have an application with business rules that 
might be affected by that, which is why I wouldn't write it that way.

> > Thus, the listening DM's need to check _p_oid directly, and also look for
> > just-added (but not flushed) objects that might reference the object 
> directly.
>How do I do that, should I checked 'self.dirty'?

Actually, I assume you'd check the inverse link(s) from the object being 

If I were going to implement cascading delete, I'd do it in the remove() 
method by traversing each attribute that is an outward delete link from the 
current object, to find the other objects that needed to be removed.  I 
would then remove() them from their corresponding DM.

I wouldn't even bother with the event stuff, except that you seemed to want 
to do it that way.  You can find out an object's DM from its _p_jar, so 
something like this:

     def remove(self, ob):
         super(MyDM, self).remove(ob)
         for other in ob.someattr:
             if other._p_jar is not None:
         for other in ob.another_cascade:
             if other._p_jar is not None:

This seems to me the simplest way to do it, and it should work correctly 
under pretty much all circumstances, I think, as long as the rest of your 
DM code is implemented properly.  The only enhancement I'd do beyond this 
is add some kind of metadata to the model attributes so I could discover 
which ones to walk and reset without having to hardcode them in 
remove().  At that point, I'd just change the base remove() to do it.

In fact, model attributes currently have an 'isComposite' attribute that 
indicates whether they are a composition link; i.e., items on the other 
side of the link are "owned" by the object.  This would probably be a good 
indication that removing the source object should result in removal of the 
target.  So, maybe something like this:

     def remove(self,ob):
         super(MyDM, self).remove(ob)
         for feature in ob.mdl_features:
             if feature.isComposite:
                 if feature.isMany:
                     items = feature.get(ob)
                     items = [feature.get(ob)]
                 for other in items:
                     if other._p_jar is not None:

This would cascade-delete all objects referenced by model attributes 
defined with 'isComposite = True' in their definition body.

More information about the PEAK mailing list