views:

46

answers:

2

Let's say you have two kinds, Message and Contact, related by a db.ListProperty of keys on Message. A user creates a message, adds some contacts as recipients, and emails the message. Later, the user deletes one of the contact entities that was a recipient of the message. Our application should delete the appropriate Contact entity, but we want to preserve the original recipient list for the message that was sent for the user's records. In essence, we want a snapshot of the message entity at the time it was sent. If we naively delete the contact entity, though, we lose snapshot integrity; if not, we are left with an invalid key.

How would you handle this situation, either in controller logic or model changes?


    class User(db.Model):
        email = db.EmailProperty(required=True)

    class Contact(db.Model): 
        email = db.EmailProperty(required=True) 
        user = db.ReferenceProperty(User, collection_name='contacts') 

    class Message(db.Model): 
        recipients = db.ListProperty(db.Key)   # contacts 
        sender = db.ReferenceProperty(User, collection_name='messages') 
        body = db.TextProperty() 
        is_emailed = db.BooleanProperty(default=False)

+2  A: 

I would add a boolean field "deleted" (or something spiffier, such as the date and time of deletion) to the Contact model -- so that contacts are never physically deleted, but rather only "logically" deleted when that field is set. (This also lets you offer other cool features such as "show my old now-deleted contacts", "undelete" functionality, etc, if you wish).

This is a common approach in all storage systems that are required to maintain historical integrity (and/or similar requirements such as "auditability").

In cases where the sheer amount of logically deleted entities is threatening to damage system performance, the classic alternative is to have a separate, identical model "DeletedContacts", but foreign key constraints require more work, e.g. the Message class would have to have both recipients and deleted_recipients fiels if you needed foreign key integrity (but using just keys, as you're doing, this extra work would not be needed).

I doubt the average user will delete such a huge percentage of their contacts as to warrant the optimization explained in the last paragraph, so in this case I'd go with the simple "deleted" field.

Alex Martelli
Thanks Alex! The "soft-delete" strategy is what we implemented (but we missed the spiffier choice). I'm glad to hear it gets your seal! :)Now I'm thinking about richer state (e.g., is_deleted, is_confirmed, is_preferred, etc.), and how coupled and complex things can get. Gotta' work up a new question for on this....
Andrew B.
A: 

Alternately, you could refactor your Contact model by moving the email address into the key name and setting the user as the parent entity. Your recipients property would change to a string list of raw email addresses. This gives you a static list of email recipients without having to fetch a set of corresponding entities for each one, or requiring that such entities still exist. If you want to fetch the contact entities, you can easily construct their keys from the user and the recipient address.

One limitation here is that the email address on an existing contact entity cannot be changed, but I think you have that problem anyway. Changing a contact address with your existing model would retroactively change the recipients of a sent message, which we know is a problem.

Drew Sears