dhruvaray/backbone-associations — pull request #92 by ronen (2013-12-30 13:31:47+00:00)
Title: Added reverseKey support for Many relations

Hi. Well, since I mouthed off in #51 about how much I wanted reverse keys, I figured I should put my money where my mouth is. So I've taken the liberty of coding up an implementation of reverseKey (for Many relations only).

Here are some notes/documentation. For the sake of discussion, assume a relation of the form

Zoo = Backbone.AssociatedModel.extend({
relations: [{
type: Backbone.Many,
key: 'animals,
relatedModel: Animal,
reverseKey: 'zoo'
}],
defaults: {
children: []
}
});

* You can add an Animal to a Zoo's "animals" collection from either direction:

animal.set("zoo", zoo)
zoo.get("animals").add(animal) // or set(animal) or reset([animal])

* You can likewise remove an Animal from a Zoo's "animals" collection from either direction:

animal.set("zoo", null)
animal.set("zoo", otherZoo) // removes from current zoo and adds to otherZoo
zoo.get("animals").remove(animal) // or reset([otherAnimal]) which implicitly removes animal
animal.destroy()

* No events are sent until the object graph is completely updated. Here's a quick rundown of the events:

* When an Animal is added to a Zoo, in addition to the "add" event, the new zoo's "animals" collection gets a "change" and a "change:zoo" event noting that the animal's "zoo" attribute has been set, with the change-related API working properly, and the events properly bubbling to the zoo model.

* When an Animal is removed from a Zoo, in addition to the "remove" event, the new zoo's "animals" collection gets a "change" and a "change:zoo" event noting that the animal's "zoo" attribute has been set, with the change-related API working properly, and the events properly bubbling to the zoo model. (Note at the time the change event is triggered, the animal is no longer in the collection; in the zoo's event path for the change event, the index refers to the index prior to removal).

* The only exception when adding is that if an Animal is created with a zoo provided as an initialization attribute, that zoo's "animals" collection gets an "add" event but no "change" events (it's not considered a change since that's the initial zoo value).

* The only exception when deleting is that if an Animal is destroyed, it's zoo's "animals" collection gets a "remove" event but no "change" events (it's not considered a change, since that's the final zoo value).

* When creating a new Animal model with a zoo provided as an initialization attribute, or when creating a new Zoo model with a set of models as an initialization attribute, the relations are not processed until after initialization of the new model. This ensures that the new model has been completely initialized before the related objects start handling it. (One quirk: within the new model's initializer, the new model's own attributes will have been set, but the related objects will not have been updated. That's arguably something that should be changed, so that the related objects *will* have been updated, although no events triggered yet.)

(Getting the events right was the trickiest part of the implementation!)

I've included a whole bunch of tests for the new behavior, and have verified that all the old tests still pass. I tried to minimize/localize the impact on the code, and of course to have minimal runtime impact if relationKey is not specified. I've tried it out in my app and it seems to be working well. Hope you like it!

And of course I'm open to suggestions If you think I did anything wrong in terms of functionality or implementation.

Happy new year :)