I see a couple of problems.
First of all, your defaults :
defaults: { "Product" : null, "ShortDescription" : null, "Category" : "food", "Price" : new PriceModel, "Date" : new DateModel, "Uses" : 0, "Tags" : [], "Contributor" : null }
This will cause one PriceModel , one DateModel and one tag array to be shared by all instances of this model. The defaults object is shallowly copied and merged into model attributes, none of the values ββin defaults are cloned or duplicated, they are simply copied as is. If you want to highlight the values Price , Date and Tags , then use the function for defaults :
defaults: function() { return { "Product" : null, "ShortDescription" : null, "Category" : "food", "Price" : new PriceModel, "Date" : new DateModel, "Uses" : 0, "Tags" : [], "Contributor" : null }; }
The second problem is that set has a fairly simplified view of what change means. If you look at the source for set , you will see the following:
// If the new and previous value differ, record the change. If not, // then remove changes for this attribute. if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) { this.changed[attr] = val; if (!silent) this._pending[attr] = true; } else { delete this.changed[attr]; delete this._pending[attr]; if (!changing) delete this._changes[attr]; }
_.isEqual will not recognize that something has changed inside your Price or Date or that you have added or removed something from Tags . If you follow these steps:
p = new PriceModel(...); m.set('Price', p)
then m will notice that Price has changed, but if you:
p = m.get('Price'); p.set(...); m.set('Price', p);
then m will not recognize that Price has changed; your model will not automatically bind to events on Price , so that it does not notice the call to p.set(...) , and it does not recognize m.set('Price', p) as a change, since this is a little more than a fancy way say p = p .
You can solve part of this change problem by not providing the array a set a Tags that came from get ; make a copy, change the copy, and then pass the updated copy to set . Half can be processed by binding to the "change" events on the Price and Date models and redirecting them in the same way as collections do, for example:
initialize: function() { this.attributes.Price.on( 'all', function(ev, model, opts) { this.trigger(ev, model, opts) }, this );
You want to provide your own implementation of set in case someone does set('Price', some_new_object) , and you need to set('Price', some_new_object) your forwarder.