I am trying to implement my version of the “store instance” in Backbone.js, as Soundcloud described in their recent blog post:
http://backstage.soundcloud.com/2012/06/building-the-next-soundcloud/
Appropriate exposure:
To solve this problem, we use a construct that we call instance storage. This store is an object that is implicitly accessible and modified every time the constructor for the model is called. When the model was first built, it is embedded in the repository using its identifier as a unique key. If the same model constructor is called with the same identifier, the original instance is returned.
var s1 = new Sound({id: 123}), s2 = new Sound({id: 123}); s1 === s2;
This works because of the surprisingly little-known Javascript feature. If the constructor returns an object, then this is the assigned value. Therefore, if we return the link to the instance created earlier, we will get the desired behavior. Behind the scenes, the constructor basically does this:
var store = {}; function Sound(attributes) { var id = attributes.id; // check if this model has already been created if (store[id]) { // if yes, return that return store[id]; } // otherwise, store this instance store[id] = this; }
I implemented my version of this by overriding the Backbone.Model class to create my own constructor.
var MyModel = Backbone.Model.extend({ constructor: function (attributes, options) { var id = attributes ? attributes.id : undefined; if (this.store[id]) { return this.store[id]; } Backbone.Model.prototype.constructor.apply(this, arguments); if (id) { this.store[id] = this; } } }); var MyOtherModel = MyModel.extend({ store: {},
This worked fine, but something had to change, and now it stopped working, and I'm not sure why. Recently created instances are stored in the storage object without any problems - each class that extends the MyModel class has its own empty storage to avoid collisions of instances of another type with the same identifier. The correct instance is also retrieved without problems when calling the constructor with an existing identifier, however, when they return from the constructor, the return value is ignored. My understanding from the specification is that constructors can return an object, but not a primitive, and the returned object will be assigned to the left side of the assignment operator when the constructor is called with a new operator. This does not happen, even if the constructor returns an object, an empty object created by the new operator is used.
Some debugging information. Not sure how useful this information will be. This is "this" in the MyModel constructor for the object that is being created for the first time.
child _callbacks: Object _escapedAttributes: Object _previousAttributes: Object _setting: false attributes: Object id: "4fd6140032a6e522f10009ac" manufacturer_id: "4f4135ae32a6e52a53000001" name: "Tide" uniqueName: "tide" __proto__: Object cid: "c50" collection: child id: "4fd6140032a6e522f10009ac" __proto__: ctor constructor: function (){ parent.apply(this, arguments); } defaults: Object store: Object url: function () { urlRoot: function () { __proto__: ctor
And this is the "this" in the MyModel constructor when the object is returned from the instance store:
child _callbacks: Object _escapedAttributes: Object _previousAttributes: Object _setting: false attributes: Object _validate: function (attrs, options) { bind: function (events, callback, context) { change: function (options) { changedAttributes: function (diff) { clear: function (options) { clone: function () { constructor: function (){ parent.apply(this, arguments); } defaults: Object destroy: function (options) { escape: function (attr) { fetch: function (options) { get: function (attr) { has: function (attr) { hasChanged: function (attr) { idAttribute: "id" initialize: function (){} isNew: function () { isValid: function () { manufacturer_id: 0 name: "" off: function (events, callback, context) { on: function (events, callback, context) { parse: function (resp, xhr) { previous: function (attr) { previousAttributes: function () { save: function (key, value, options) { set: function (key, value, options) { store: Object toJSON: function () { trigger: function (events) { unbind: function (events, callback, context) { unset: function (attr, options) { url: function () { urlRoot: function () { __proto__: Object cid: "c141" __proto__: ctor constructor: function (){ parent.apply(this, arguments); } defaults: Object store: Object url: function () { urlRoot: function () { __proto__: ctor
I note that the attribute object in the second has all the methods of the base object included in it, which they should not be. It also has no identifier, again I'm not sure why. Hope this gives some insight. Thanks.