Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Updated performance comparison section

  • Loading branch information...
commit 0c3864112c38e2c7613d51a82c65f2fdd41b9b18 1 parent 47a4df7
@dhruvaray authored
View
6 README.md
@@ -38,7 +38,7 @@ Backbone-associations depends on [backbone](https://github.com/documentcloud/bac
<script type="text/javascript" src="./js/backbone-associations.js"></script>
```
-Backbone-associations works with Backbone v0.9.10. Underscore v1.4.3 upwards is supported.
+Backbone-associations works with Backbone v0.9.10 upwards. Underscore v1.4.3 upwards is supported.
## <a name="associations"/>Specifying Associations
@@ -566,9 +566,9 @@ Refer to issue [#28](https://github.com/dhruvaray/backbone-associations/issues/2
## <a name="performance"/>Performance Comparison
-![Performance](http://dhruvaray.github.com/backbone-associations/docs/img/speed0.4.1.png)
+![Performance](http://dhruvaray.github.com/backbone-associations/docs/img/speed0.4.2.png)
-Each operation comprises of n (10, 15, 20, 25, 30) inserts. The chart above compares the performance (time and operations/sec) of the two implementations. (backbone-associations v0.4.1 v/s backbone-relational v0.7.1)
+Each operation comprises of n (10, 15, 20, 25, 30) inserts. The chart above compares the performance (time and operations/sec) of the two implementations. (backbone-associations v0.4.2 v/s backbone-relational v0.8.0+ [2fff8e](https://github.com/PaulUithol/Backbone-relational/tree/4da5586bc92f5e6a63d725f26fe7f1b66c2fff8e))
Run tests on your machine configuration instantly [here](http://dhruvaray.github.com/backbone-associations/test/speed-comparison.html)
View
BIN  docs/img/speed0.4.2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
209 test/lib/backbone-relational.js
@@ -1,6 +1,6 @@
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
/**
- * Backbone-relational.js 0.8.0
+ * Backbone-relational.js 0.8.0+
* (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
*
* Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
@@ -150,6 +150,14 @@
},
/**
+ * Remove a scope.
+ * @param {Object} scope
+ */
+ removeModelScope: function( scope ) {
+ this._modelScopes = _.without( this._modelScopes, scope );
+ },
+
+ /**
* Add a set of subModelTypes to the store, that can be used to resolve the '_superModel'
* for a model later in 'setupSuperModel'.
*
@@ -367,7 +375,7 @@
},
/**
- *
+ * Find a specific model of a certain `type` in the store
* @param type
* @param {String|Number|Object|Backbone.RelationalModel} item
*/
@@ -396,13 +404,6 @@
var coll = this.getCollection( model );
if ( coll ) {
- if ( coll.get( model ) ) {
- if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
- console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', coll.get( model ), model );
- }
- throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
- }
-
var modelColl = model.collection;
coll.add( model );
this.listenTo( model, 'destroy', this.unregister, this );
@@ -411,12 +412,34 @@
},
/**
+ * Check if the given model may use the given `id`
+ * @param model
+ * @param [id]
+ */
+ checkId: function( model, id ) {
+ var coll = this.getCollection( model ),
+ duplicate = coll && coll.get( id );
+
+ if ( duplicate && model !== duplicate ) {
+ if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
+ console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', duplicate, model );
+ }
+
+ throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
+ }
+ },
+
+ /**
* Explicitly update a model's id in its store collection
* @param {Backbone.RelationalModel} model
*/
update: function( model ) {
var coll = this.getCollection( model );
+ // This triggers updating the lookup indices kept in a collection
coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
+
+ // Trigger an event on model so related models (having the model's new id in their keyContents) can add it.
+ model.trigger( 'relational:change:id', model, coll );
},
/**
@@ -519,7 +542,7 @@
// When 'relatedModel' are created or destroyed, check if it affects this relation.
this.listenTo( this.instance, 'destroy', this.destroy )
- .listenTo( this.relatedCollection, 'relational:add', this.tryAddRelated )
+ .listenTo( this.relatedCollection, 'relational:add relational:change:id', this.tryAddRelated )
.listenTo( this.relatedCollection, 'relational:remove', this.removeRelated )
}
};
@@ -688,6 +711,11 @@
related = this.relatedModel.findOrCreate( this.keyContents, opts );
}
+ // Nullify `keyId` if we have a related model; in case it was already part of the relation
+ if ( this.related ) {
+ this.keyId = null;
+ }
+
return related;
},
@@ -804,7 +832,7 @@
if ( _.isString( this.collectionType ) ) {
this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
}
- if ( !this.collectionType.prototype instanceof Backbone.Collection ){
+ if ( this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
throw new Error( '`collectionType` must inherit from Backbone.Collection' );
}
@@ -878,7 +906,9 @@
}
else {
// If `merge` is true, update models here, instead of during update.
- model = this.relatedModel.findOrCreate( attributes, _.extend( { merge: true }, options, { create: this.options.createModels } ) );
+ model = this.relatedModel.findOrCreate( attributes,
+ _.extend( { merge: true }, options, { create: this.options.createModels } )
+ );
}
model && toAdd.push( model );
@@ -891,9 +921,14 @@
related = this._prepareCollection();
}
- related.update( toAdd, _.defaults( { merge: false, parse: false }, options ) );
+ // By now, both `merge` and `parse` will already have been executed for models if they were specified.
+ // Disable them to prevent additional calls.
+ related.set( toAdd, _.defaults( { merge: false, parse: false }, options ) );
}
+ // Remove entries from `keyIds` that were already part of the relation (and are thus 'unchanged')
+ this.keyIds = _.difference( this.keyIds, _.pluck( related.models, 'id' ) );
+
return related;
},
@@ -1004,7 +1039,7 @@
var dit = this;
model.queue( function() {
if ( dit.related && !dit.related.get( model ) ) {
- dit.related.add( model, options );
+ dit.related.add( model, _.defaults( { parse: false }, options ) );
}
});
},
@@ -1037,12 +1072,12 @@
// Nasty hack, for cases like 'model.get( <HasMany key> ).add( item )'.
// Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
// collection events only after the model is really fully set up.
- // Example: "p.get('jobs').add( { company: c, person: p } )".
+ // Example: event for "p.on( 'add:jobs' )" -> "p.get('jobs').add( { company: c.id, person: p.id } )".
if ( options && options.collection ) {
var dit = this,
collection = this.collection = options.collection;
- // Prevent this option from cascading down to related models; they shouldn't go into this `if` clause.
+ // Prevent `collection` from cascading down to nested models; they shouldn't go into this `if` clause.
delete options.collection;
this._deferProcessing = true;
@@ -1209,15 +1244,22 @@
var setUrl,
requests = [],
rel = this.getRelation( key ),
- keys = rel && ( rel.keyIds || [ rel.keyId ] ),
- toFetch = keys && _.select( keys || [], function( id ) {
- return ( id || id === 0 ) && ( refresh || !Backbone.Relational.store.find( rel.relatedModel, id ) );
- }, this );
+ idsToFetch = rel && ( rel.keyIds || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) );
+
+ // On `refresh`, add the ids for current models in the relation to `idsToFetch`
+ if ( refresh ) {
+ var models = rel.related instanceof Backbone.Collection ? rel.related.models : [ rel.related ];
+ _.each( models, function( model ) {
+ if ( model.id || model.id === 0 ) {
+ idsToFetch.push( model.id );
+ }
+ });
+ }
- if ( toFetch && toFetch.length ) {
+ if ( idsToFetch && idsToFetch.length ) {
// Find (or create) a model for each one that is to be fetched
var created = [],
- models = _.map( toFetch, function( id ) {
+ models = _.map( idsToFetch, function( id ) {
var model = Backbone.Relational.store.find( rel.relatedModel, id );
if ( !model ) {
@@ -1315,19 +1357,23 @@
attributes[ key ] = value;
}
- var result = Backbone.Model.prototype.set.apply( this, arguments );
-
- // Ideal place to set up relations :)
try {
+ var id = this.id,
+ newId = attributes && this.idAttribute in attributes && attributes[ this.idAttribute ];
+
+ // Check if we're not setting a duplicate id before actually calling `set`.
+ Backbone.Relational.store.checkId( this, newId );
+
+ var result = Backbone.Model.prototype.set.apply( this, arguments );
+
+ // Ideal place to set up relations, if this is the first time we're here for this model
if ( !this._isInitialized && !this.isLocked() ) {
this.constructor.initializeModelHierarchy();
-
Backbone.Relational.store.register( this );
-
this.initializeRelations( options );
}
- // Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true}
- else if ( attributes && this.idAttribute in attributes ) {
+ // The store should know about an `id` update asap
+ else if ( newId && newId !== id ) {
Backbone.Relational.store.update( this );
}
@@ -1397,54 +1443,59 @@
}
_.each( this._relations, function( rel ) {
- var value = json[ rel.key ];
+ var related = json[ rel.key ],
+ includeInJSON = rel.options.includeInJSON,
+ value = null;
- if ( rel.options.includeInJSON === true) {
- if ( value && _.isFunction( value.toJSON ) ) {
- json[ rel.keyDestination ] = value.toJSON( options );
- }
- else {
- json[ rel.keyDestination ] = null;
+ if ( includeInJSON === true ) {
+ if ( related && _.isFunction( related.toJSON ) ) {
+ value = related.toJSON( options );
}
}
- else if ( _.isString( rel.options.includeInJSON ) ) {
- if ( value instanceof Backbone.Collection ) {
- json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
+ else if ( _.isString( includeInJSON ) ) {
+ if ( related instanceof Backbone.Collection ) {
+ value = related.pluck( includeInJSON );
}
- else if ( value instanceof Backbone.Model ) {
- json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
+ else if ( related instanceof Backbone.Model ) {
+ value = related.get( includeInJSON );
}
- else {
- json[ rel.keyDestination ] = null;
+
+ // Add ids for 'unfound' models if includeInJSON is equal to (only) the relatedModel's `idAttribute`
+ if ( includeInJSON === rel.relatedModel.prototype.idAttribute ) {
+ if ( rel instanceof Backbone.HasMany ) {
+ value = value.concat( rel.keyIds );
+ }
+ else if ( rel instanceof Backbone.HasOne ) {
+ value = value || rel.keyId;
+ }
}
}
- else if ( _.isArray( rel.options.includeInJSON ) ) {
- if ( value instanceof Backbone.Collection ) {
- var valueSub = [];
- value.each( function( model ) {
+ else if ( _.isArray( includeInJSON ) ) {
+ if ( related instanceof Backbone.Collection ) {
+ value = [];
+ related.each( function( model ) {
var curJson = {};
- _.each( rel.options.includeInJSON, function( key ) {
+ _.each( includeInJSON, function( key ) {
curJson[ key ] = model.get( key );
});
- valueSub.push( curJson );
+ value.push( curJson );
});
- json[ rel.keyDestination ] = valueSub;
}
- else if ( value instanceof Backbone.Model ) {
- var valueSub = {};
- _.each( rel.options.includeInJSON, function( key ) {
- valueSub[ key ] = value.get( key );
+ else if ( related instanceof Backbone.Model ) {
+ value = {};
+ _.each( includeInJSON, function( key ) {
+ value[ key ] = related.get( key );
});
- json[ rel.keyDestination ] = valueSub;
- }
- else {
- json[ rel.keyDestination ] = null;
}
}
else {
delete json[ rel.key ];
}
+ if ( includeInJSON ) {
+ json[ rel.keyDestination ] = value;
+ }
+
if ( rel.keyDestination !== rel.key ) {
delete json[ rel.key ];
}
@@ -1548,17 +1599,15 @@
// inherited automatically (due to a redefinition of 'relations').
// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail
// the isUndefined/isNull check next time.
- if ( this._superModel ) {
- //
- if ( this._superModel.prototype.relations ) {
- var supermodelRelationsExist = _.any( this.prototype.relations || [], function( rel ) {
- return rel.model && rel.model !== this;
+ if ( this._superModel && this._superModel.prototype.relations ) {
+ // Find relations that exist on the `_superModel`, but not yet on this model.
+ var inheritedRelations = _.select( this._superModel.prototype.relations || [], function( superRel ) {
+ return !_.any( this.prototype.relations || [], function( rel ) {
+ return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
}, this );
+ }, this );
- if ( !supermodelRelationsExist ) {
- this.prototype.relations = this._superModel.prototype.relations.concat( this.prototype.relations );
- }
- }
+ this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
}
else {
this._superModel = false;
@@ -1598,6 +1647,9 @@
// If not, create an instance (unless 'options.create' is false).
if ( _.isObject( attributes ) ) {
if ( model && options.merge !== false ) {
+ // Make sure `options.collection` doesn't cascade to nested models
+ delete options.collection;
+
model.set( parsedAttributes, options );
}
else if ( !model && options.create !== false ) {
@@ -1627,7 +1679,7 @@
model = attrs;
}
else {
- options || (options = {});
+ options || ( options = {} );
options.collection = this;
if ( typeof this.model.findOrCreate !== 'undefined' ) {
@@ -1648,19 +1700,23 @@
/**
- * Override Backbone.Collection.add, so we'll create objects from attributes where required,
+ * Override Backbone.Collection.set, so we'll create objects from attributes where required,
* and update the existing models. Also, trigger 'relational:add'.
*/
- var add = Backbone.Collection.prototype.__add = Backbone.Collection.prototype.add;
- Backbone.Collection.prototype.add = function( models, options ) {
+ var set = Backbone.Collection.prototype.__set = Backbone.Collection.prototype.set;
+ Backbone.Collection.prototype.set = function( models, options ) {
// Short-circuit if this Collection doesn't hold RelationalModels
if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
- return add.apply( this, arguments );
+ return set.apply( this, arguments );
}
- models = _.isArray( models ) ? models.slice() : [ models ];
- // Set default options to the same values as `add` uses, so `findOrCreate` will also respect those.
- options = _.extend( { merge: false }, options );
+ if ( options && options.parse ) {
+ models = this.parse( models, options );
+ }
+
+ if ( !_.isArray( models ) ) {
+ models = models ? [ models ] : [];
+ }
var newModels = [],
toAdd = [];
@@ -1686,7 +1742,8 @@
}, this );
// Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
- add.call( this, toAdd, options );
+ // If `parse` was specified, the collection and contained models have been parsed now.
+ set.call( this, toAdd, _.defaults( { parse: false }, options ) );
_.each( newModels, function( model ) {
// Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
View
2  test/lib/underscore.js
@@ -1224,4 +1224,4 @@
});
-}).call(this);
+}).call(this);
Please sign in to comment.
Something went wrong with that request. Please try again.