web/res/js/backbone-relational.js
changeset 1512 487ca37bb0c7
parent 872 6b7c534792b7
equal deleted inserted replaced
1511:25731d245eda 1512:487ca37bb0c7
     1 /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
     1 /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
     2 /**
     2 /**
     3  * Backbone-relational.js 0.8.5
     3  * Backbone-relational.js 0.10.0
     4  * (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
     4  * (c) 2011-2014 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
     5  * 
     5  *
     6  * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
     6  * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
     7  * For details and documentation: https://github.com/PaulUithol/Backbone-relational.
     7  * For details and documentation: https://github.com/PaulUithol/Backbone-relational.
     8  * Depends on Backbone (and thus on Underscore as well): https://github.com/documentcloud/backbone.
     8  * Depends on Backbone (and thus on Underscore as well): https://github.com/documentcloud/backbone.
       
     9  *
       
    10  * Example:
       
    11  *
       
    12 	Zoo = Backbone.RelationalModel.extend({
       
    13 		relations: [ {
       
    14 			type: Backbone.HasMany,
       
    15 			key: 'animals',
       
    16 			relatedModel: 'Animal',
       
    17 			reverseRelation: {
       
    18 				key: 'livesIn',
       
    19 				includeInJSON: 'id'
       
    20 				// 'relatedModel' is automatically set to 'Zoo'; the 'relationType' to 'HasOne'.
       
    21 			}
       
    22 		} ],
       
    23 
       
    24 		toString: function() {
       
    25 			return this.get( 'name' );
       
    26 		}
       
    27 	});
       
    28 
       
    29 	Animal = Backbone.RelationalModel.extend({
       
    30 		toString: function() {
       
    31 			return this.get( 'species' );
       
    32 		}
       
    33 	});
       
    34 
       
    35 	// Creating the zoo will give it a collection with one animal in it: the monkey.
       
    36 	// The animal created after that has a relation `livesIn` that points to the zoo it's currently associated with.
       
    37 	// If you instantiate (or fetch) the zebra later, it will automatically be added.
       
    38 
       
    39 	var zoo = new Zoo({
       
    40 		name: 'Artis',
       
    41 		animals: [ { id: 'monkey-1', species: 'Chimp' }, 'lion-1', 'zebra-1' ]
       
    42 	});
       
    43 
       
    44 	var lion = new Animal( { id: 'lion-1', species: 'Lion' } ),
       
    45 		monkey = zoo.get( 'animals' ).first(),
       
    46 		sameZoo = lion.get( 'livesIn' );
     9  */
    47  */
    10 ( function( undefined ) {
    48 ( function( root, factory ) {
       
    49 	// Set up Backbone-relational for the environment. Start with AMD.
       
    50 	if ( typeof define === 'function' && define.amd ) {
       
    51 		define( [ 'exports', 'backbone', 'underscore' ], factory );
       
    52 	}
       
    53 	// Next for Node.js or CommonJS.
       
    54 	else if ( typeof exports !== 'undefined' ) {
       
    55 		factory( exports, require( 'backbone' ), require( 'underscore' ) );
       
    56 	}
       
    57 	// Finally, as a browser global. Use `root` here as it references `window`.
       
    58 	else {
       
    59 		factory( root, root.Backbone, root._ );
       
    60 	}
       
    61 }( this, function( exports, Backbone, _ ) {
    11 	"use strict";
    62 	"use strict";
    12 
       
    13 	/**
       
    14 	 * CommonJS shim
       
    15 	 **/
       
    16 	var _, Backbone, exports;
       
    17 	if ( typeof window === 'undefined' ) {
       
    18 		_ = require( 'underscore' );
       
    19 		Backbone = require( 'backbone' );
       
    20 		exports = module.exports = Backbone;
       
    21 	}
       
    22 	else {
       
    23 		_ = window._;
       
    24 		Backbone = window.Backbone;
       
    25 		exports = window;
       
    26 	}
       
    27 
    63 
    28 	Backbone.Relational = {
    64 	Backbone.Relational = {
    29 		showWarnings: true
    65 		showWarnings: true
    30 	};
    66 	};
    31 
    67 
    84 			else {
   120 			else {
    85 				func();
   121 				func();
    86 			}
   122 			}
    87 		},
   123 		},
    88 
   124 
       
   125 		// Some of the queued events may trigger other blocking events. By
       
   126 		// copying the queue here it allows queued events to process closer to
       
   127 		// the natural order.
       
   128 		//
       
   129 		// queue events [ 'A', 'B', 'C' ]
       
   130 		// A handler of 'B' triggers 'D' and 'E'
       
   131 		// By copying `this._queue` this executes:
       
   132 		// [ 'A', 'B', 'D', 'E', 'C' ]
       
   133 		// The same order the would have executed if they didn't have to be
       
   134 		// delayed and queued.
    89 		process: function() {
   135 		process: function() {
    90 			while ( this._queue && this._queue.length ) {
   136 			var queue = this._queue;
    91 				this._queue.shift()();
   137 			this._queue = [];
       
   138 			while ( queue && queue.length ) {
       
   139 				queue.shift()();
    92 			}
   140 			}
    93 		},
   141 		},
    94 
   142 
    95 		block: function() {
   143 		block: function() {
    96 			this.acquire();
   144 			this.acquire();
   132 		 * @param {Object} [options]
   180 		 * @param {Object} [options]
   133 		 */
   181 		 */
   134 		initializeRelation: function( model, relation, options ) {
   182 		initializeRelation: function( model, relation, options ) {
   135 			var type = !_.isString( relation.type ) ? relation.type : Backbone[ relation.type ] || this.getObjectByName( relation.type );
   183 			var type = !_.isString( relation.type ) ? relation.type : Backbone[ relation.type ] || this.getObjectByName( relation.type );
   136 			if ( type && type.prototype instanceof Backbone.Relation ) {
   184 			if ( type && type.prototype instanceof Backbone.Relation ) {
   137 				new type( model, relation, options ); // Also pushes the new Relation into `model._relations`
   185 				var rel = new type( model, relation, options ); // Also pushes the new Relation into `model._relations`
   138 			}
   186 			}
   139 			else {
   187 			else {
   140 				Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid relation type!', relation );
   188 				Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid relation type!', relation );
   141 			}
   189 			}
   142 		},
   190 		},
   177 		 *
   225 		 *
   178 		 * @param {Backbone.RelationalModel} modelType
   226 		 * @param {Backbone.RelationalModel} modelType
   179 		 */
   227 		 */
   180 		setupSuperModel: function( modelType ) {
   228 		setupSuperModel: function( modelType ) {
   181 			_.find( this._subModels, function( subModelDef ) {
   229 			_.find( this._subModels, function( subModelDef ) {
   182 				return _.find( subModelDef.subModels || [], function( subModelTypeName, typeValue ) {
   230 				return _.filter( subModelDef.subModels || [], function( subModelTypeName, typeValue ) {
   183 					var subModelType = this.getObjectByName( subModelTypeName );
   231 					var subModelType = this.getObjectByName( subModelTypeName );
   184 
   232 
   185 					if ( modelType === subModelType ) {
   233 					if ( modelType === subModelType ) {
   186 						// Set 'modelType' as a child of the found superModel
   234 						// Set 'modelType' as a child of the found superModel
   187 						subModelDef.superModelType._subModels[ typeValue ] = modelType;
   235 						subModelDef.superModelType._subModels[ typeValue ] = modelType;
   190 						modelType._superModel = subModelDef.superModelType;
   238 						modelType._superModel = subModelDef.superModelType;
   191 						modelType._subModelTypeValue = typeValue;
   239 						modelType._subModelTypeValue = typeValue;
   192 						modelType._subModelTypeAttribute = subModelDef.superModelType.prototype.subModelTypeAttribute;
   240 						modelType._subModelTypeAttribute = subModelDef.superModelType.prototype.subModelTypeAttribute;
   193 						return true;
   241 						return true;
   194 					}
   242 					}
   195 				}, this );
   243 				}, this ).length;
   196 			}, this );
   244 			}, this );
   197 		},
   245 		},
   198 
   246 
   199 		/**
   247 		/**
   200 		 * Add a reverse relation. Is added to the 'relations' property on model's prototype, and to
   248 		 * Add a reverse relation. Is added to the 'relations' property on model's prototype, and to
   209 			var exists = _.any( this._reverseRelations, function( rel ) {
   257 			var exists = _.any( this._reverseRelations, function( rel ) {
   210 				return _.all( relation || [], function( val, key ) {
   258 				return _.all( relation || [], function( val, key ) {
   211 					return val === rel[ key ];
   259 					return val === rel[ key ];
   212 				});
   260 				});
   213 			});
   261 			});
   214 			
   262 
   215 			if ( !exists && relation.model && relation.type ) {
   263 			if ( !exists && relation.model && relation.type ) {
   216 				this._reverseRelations.push( relation );
   264 				this._reverseRelations.push( relation );
   217 				this._addRelation( relation.model, relation );
   265 				this._addRelation( relation.model, relation );
   218 				this.retroFitRelation( relation );
   266 				this.retroFitRelation( relation );
   219 			}
   267 			}
   276 			coll && coll.each( function( model ) {
   324 			coll && coll.each( function( model ) {
   277 				if ( !( model instanceof relation.model ) ) {
   325 				if ( !( model instanceof relation.model ) ) {
   278 					return;
   326 					return;
   279 				}
   327 				}
   280 
   328 
   281 				new relation.type( model, relation );
   329 				var rel = new relation.type( model, relation );
   282 			}, this );
   330 			}, this );
   283 		},
   331 		},
   284 
   332 
   285 		/**
   333 		/**
   286 		 * Find the Store's collection for a certain type of model.
   334 		 * Find the Store's collection for a certain type of model.
   290 		 */
   338 		 */
   291 		getCollection: function( type, create ) {
   339 		getCollection: function( type, create ) {
   292 			if ( type instanceof Backbone.RelationalModel ) {
   340 			if ( type instanceof Backbone.RelationalModel ) {
   293 				type = type.constructor;
   341 				type = type.constructor;
   294 			}
   342 			}
   295 			
   343 
   296 			var rootModel = type;
   344 			var rootModel = type;
   297 			while ( rootModel._superModel ) {
   345 			while ( rootModel._superModel ) {
   298 				rootModel = rootModel._superModel;
   346 				rootModel = rootModel._superModel;
   299 			}
   347 			}
   300 			
   348 
   301 			var coll = _.findWhere( this._collections, { model: rootModel } );
   349 			var coll = _.find( this._collections, function( item ) {
   302 			
   350 				return item.model === rootModel;
       
   351 			});
       
   352 
   303 			if ( !coll && create !== false ) {
   353 			if ( !coll && create !== false ) {
   304 				coll = this._createCollection( rootModel );
   354 				coll = this._createCollection( rootModel );
   305 			}
   355 			}
   306 			
   356 
   307 			return coll;
   357 			return coll;
   308 		},
   358 		},
   309 
   359 
   310 		/**
   360 		/**
   311 		 * Find a model type on one of the modelScopes by name. Names are split on dots.
   361 		 * Find a model type on one of the modelScopes by name. Names are split on dots.
   329 			return type;
   379 			return type;
   330 		},
   380 		},
   331 
   381 
   332 		_createCollection: function( type ) {
   382 		_createCollection: function( type ) {
   333 			var coll;
   383 			var coll;
   334 			
   384 
   335 			// If 'type' is an instance, take its constructor
   385 			// If 'type' is an instance, take its constructor
   336 			if ( type instanceof Backbone.RelationalModel ) {
   386 			if ( type instanceof Backbone.RelationalModel ) {
   337 				type = type.constructor;
   387 				type = type.constructor;
   338 			}
   388 			}
   339 			
   389 
   340 			// Type should inherit from Backbone.RelationalModel.
   390 			// Type should inherit from Backbone.RelationalModel.
   341 			if ( type.prototype instanceof Backbone.RelationalModel ) {
   391 			if ( type.prototype instanceof Backbone.RelationalModel ) {
   342 				coll = new Backbone.Collection();
   392 				coll = new Backbone.Collection();
   343 				coll.model = type;
   393 				coll.model = type;
   344 				
   394 
   345 				this._collections.push( coll );
   395 				this._collections.push( coll );
   346 			}
   396 			}
   347 			
   397 
   348 			return coll;
   398 			return coll;
   349 		},
   399 		},
   350 
   400 
   351 		/**
   401 		/**
   352 		 * Find the attribute that is to be used as the `id` on a given object
   402 		 * Find the attribute that is to be used as the `id` on a given object
   378 		 * Find a specific model of a certain `type` in the store
   428 		 * Find a specific model of a certain `type` in the store
   379 		 * @param type
   429 		 * @param type
   380 		 * @param {String|Number|Object|Backbone.RelationalModel} item
   430 		 * @param {String|Number|Object|Backbone.RelationalModel} item
   381 		 */
   431 		 */
   382 		find: function( type, item ) {
   432 		find: function( type, item ) {
   383 			var id = this.resolveIdForItem( type, item );
   433 			var id = this.resolveIdForItem( type, item ),
   384 			var coll = this.getCollection( type );
   434 				coll = this.getCollection( type );
   385 			
   435 
   386 			// Because the found object could be of any of the type's superModel
   436 			// Because the found object could be of any of the type's superModel
   387 			// types, only return it if it's actually of the type asked for.
   437 			// types, only return it if it's actually of the type asked for.
   388 			if ( coll ) {
   438 			if ( coll ) {
   389 				var obj = coll.get( id );
   439 				var obj = coll.get( id );
   390 
   440 
   404 			var coll = this.getCollection( model );
   454 			var coll = this.getCollection( model );
   405 
   455 
   406 			if ( coll ) {
   456 			if ( coll ) {
   407 				var modelColl = model.collection;
   457 				var modelColl = model.collection;
   408 				coll.add( model );
   458 				coll.add( model );
   409 				this.listenTo( model, 'destroy', this.unregister, this );
       
   410 				model.collection = modelColl;
   459 				model.collection = modelColl;
   411 			}
   460 			}
   412 		},
   461 		},
   413 
   462 
   414 		/**
   463 		/**
   433 		 * Explicitly update a model's id in its store collection
   482 		 * Explicitly update a model's id in its store collection
   434 		 * @param {Backbone.RelationalModel} model
   483 		 * @param {Backbone.RelationalModel} model
   435 		 */
   484 		 */
   436 		update: function( model ) {
   485 		update: function( model ) {
   437 			var coll = this.getCollection( model );
   486 			var coll = this.getCollection( model );
       
   487 
       
   488 			// Register a model if it isn't yet (which happens if it was created without an id).
       
   489 			if ( !coll.contains( model ) ) {
       
   490 				this.register( model );
       
   491 			}
       
   492 
   438 			// This triggers updating the lookup indices kept in a collection
   493 			// This triggers updating the lookup indices kept in a collection
   439 			coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
   494 			coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
   440 
   495 
   441 			// Trigger an event on model so related models (having the model's new id in their keyContents) can add it.
   496 			// Trigger an event on model so related models (having the model's new id in their keyContents) can add it.
   442 			model.trigger( 'relational:change:id', model, coll );
   497 			model.trigger( 'relational:change:id', model, coll );
   443 		},
   498 		},
   444 
   499 
   445 		/**
   500 		/**
   446 		 * Remove a 'model' from the store.
   501 		 * Unregister from the store: a specific model, a collection, or a model type.
   447 		 * @param {Backbone.RelationalModel} model
   502 		 * @param {Backbone.RelationalModel|Backbone.RelationalModel.constructor|Backbone.Collection} type
   448 		 */
   503 		 */
   449 		unregister: function( model ) {
   504 		unregister: function( type ) {
   450 			this.stopListening( model, 'destroy', this.unregister );
   505 			var coll,
   451 			var coll = this.getCollection( model );
   506 				models;
   452 			coll && coll.remove( model );
   507 
       
   508 			if ( type instanceof Backbone.Model ) {
       
   509 				coll = this.getCollection( type );
       
   510 				models = [ type ];
       
   511 			}
       
   512 			else if ( type instanceof Backbone.Collection ) {
       
   513 				coll = this.getCollection( type.model );
       
   514 				models = _.clone( type.models );
       
   515 			}
       
   516 			else {
       
   517 				coll = this.getCollection( type );
       
   518 				models = _.clone( coll.models );
       
   519 			}
       
   520 
       
   521 			_.each( models, function( model ) {
       
   522 				this.stopListening( model );
       
   523 				_.invoke( model.getRelations(), 'stopListening' );
       
   524 			}, this );
       
   525 
       
   526 
       
   527 			// If we've unregistered an entire store collection, reset the collection (which is much faster).
       
   528 			// Otherwise, remove each model one by one.
       
   529 			if ( _.contains( this._collections, type ) ) {
       
   530 				coll.reset( [] );
       
   531 			}
       
   532 			else {
       
   533 				_.each( models, function( model ) {
       
   534 					if ( coll.get( model ) ) {
       
   535 						coll.remove( model );
       
   536 					}
       
   537 					else {
       
   538 						coll.trigger( 'relational:remove', model, coll );
       
   539 					}
       
   540 				}, this );
       
   541 			}
   453 		},
   542 		},
   454 
   543 
   455 		/**
   544 		/**
   456 		 * Reset the `store` to it's original state. The `reverseRelations` are kept though, since attempting to
   545 		 * Reset the `store` to it's original state. The `reverseRelations` are kept though, since attempting to
   457 		 * re-initialize these on models would lead to a large amount of warnings.
   546 		 * re-initialize these on models would lead to a large amount of warnings.
   458 		 */
   547 		 */
   459 		reset: function() {
   548 		reset: function() {
   460 			this.stopListening();
   549 			this.stopListening();
       
   550 
       
   551 			// Unregister each collection to remove event listeners
       
   552 			_.each( this._collections, function( coll ) {
       
   553 				this.unregister( coll );
       
   554 			}, this );
       
   555 
   461 			this._collections = [];
   556 			this._collections = [];
   462 			this._subModels = [];
   557 			this._subModels = [];
   463 			this._modelScopes = [ exports ];
   558 			this._modelScopes = [ exports ];
   464 		}
   559 		}
   465 	});
   560 	});
   494 		this.key = this.options.key;
   589 		this.key = this.options.key;
   495 		this.keySource = this.options.keySource || this.key;
   590 		this.keySource = this.options.keySource || this.key;
   496 		this.keyDestination = this.options.keyDestination || this.keySource || this.key;
   591 		this.keyDestination = this.options.keyDestination || this.keySource || this.key;
   497 
   592 
   498 		this.model = this.options.model || this.instance.constructor;
   593 		this.model = this.options.model || this.instance.constructor;
       
   594 
   499 		this.relatedModel = this.options.relatedModel;
   595 		this.relatedModel = this.options.relatedModel;
       
   596 
       
   597 		// No 'relatedModel' is interpreted as self-referential
       
   598 		if ( _.isUndefined( this.relatedModel ) ) {
       
   599 			this.relatedModel = this.model;
       
   600 		}
       
   601 
       
   602 		// Otherwise, try to resolve the given value to an object
       
   603 		if ( _.isFunction( this.relatedModel ) && !( this.relatedModel.prototype instanceof Backbone.RelationalModel ) ) {
       
   604 			this.relatedModel = _.result( this, 'relatedModel' );
       
   605 		}
   500 		if ( _.isString( this.relatedModel ) ) {
   606 		if ( _.isString( this.relatedModel ) ) {
   501 			this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
   607 			this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
   502 		}
   608 		}
       
   609 
   503 
   610 
   504 		if ( !this.checkPreconditions() ) {
   611 		if ( !this.checkPreconditions() ) {
   505 			return;
   612 			return;
   506 		}
   613 		}
   507 
   614 
   517 			) );
   624 			) );
   518 		}
   625 		}
   519 
   626 
   520 		if ( instance ) {
   627 		if ( instance ) {
   521 			var contentKey = this.keySource;
   628 			var contentKey = this.keySource;
   522 			if ( contentKey !== this.key && typeof this.instance.get( this.key ) === 'object' ) {
   629 			if ( contentKey !== this.key && _.isObject( this.instance.get( this.key ) ) ) {
   523 				contentKey = this.key;
   630 				contentKey = this.key;
   524 			}
   631 			}
   525 
   632 
   526 			this.setKeyContents( this.instance.get( contentKey ) );
   633 			this.setKeyContents( this.instance.get( contentKey ) );
   527 			this.relatedCollection = Backbone.Relational.store.getCollection( this.relatedModel );
   634 			this.relatedCollection = Backbone.Relational.store.getCollection( this.relatedModel );
   528 
   635 
   529 			// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
   636 			// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
   530 			if ( this.keySource !== this.key ) {
   637 			if ( this.keySource !== this.key ) {
   531 				this.instance.unset( this.keySource, { silent: true } );
   638 				delete this.instance.attributes[ this.keySource ];
   532 			}
   639 			}
   533 
   640 
   534 			// Add this Relation to instance._relations
   641 			// Add this Relation to instance._relations
   535 			this.instance._relations[ this.key ] = this;
   642 			this.instance._relations[ this.key ] = this;
   536 
   643 
   537 			this.initialize( opts );
   644 			this.initialize( opts );
   538 
   645 
   539 			if ( this.options.autoFetch ) {
   646 			if ( this.options.autoFetch ) {
   540 				this.instance.fetchRelated( this.key, _.isObject( this.options.autoFetch ) ? this.options.autoFetch : {} );
   647 				this.instance.getAsync( this.key, _.isObject( this.options.autoFetch ) ? this.options.autoFetch : {} );
   541 			}
   648 			}
   542 
   649 
   543 			// When 'relatedModel' are created or destroyed, check if it affects this relation.
   650 			// When 'relatedModel' are created or destroyed, check if it affects this relation.
   544 			this.listenTo( this.instance, 'destroy', this.destroy )
   651 			this.listenTo( this.instance, 'destroy', this.destroy )
   545 				.listenTo( this.relatedCollection, 'relational:add relational:change:id', this.tryAddRelated )
   652 				.listenTo( this.relatedCollection, 'relational:add relational:change:id', this.tryAddRelated )
   546 				.listenTo( this.relatedCollection, 'relational:remove', this.removeRelated )
   653 				.listenTo( this.relatedCollection, 'relational:remove', this.removeRelated );
   547 		}
   654 		}
   548 	};
   655 	};
   549 	// Fix inheritance :\
   656 	// Fix inheritance :\
   550 	Backbone.Relation.extend = Backbone.Model.extend;
   657 	Backbone.Relation.extend = Backbone.Model.extend;
   551 	// Set up all inheritable **Backbone.Relation** properties and methods.
   658 	// Set up all inheritable **Backbone.Relation** properties and methods.
   616 		 * Set the related model(s) for this relation
   723 		 * Set the related model(s) for this relation
   617 		 * @param {Backbone.Model|Backbone.Collection} related
   724 		 * @param {Backbone.Model|Backbone.Collection} related
   618 		 */
   725 		 */
   619 		setRelated: function( related ) {
   726 		setRelated: function( related ) {
   620 			this.related = related;
   727 			this.related = related;
   621 
       
   622 			this.instance.acquire();
       
   623 			this.instance.attributes[ this.key ] = related;
   728 			this.instance.attributes[ this.key ] = related;
   624 			this.instance.release();
       
   625 		},
   729 		},
   626 
   730 
   627 		/**
   731 		/**
   628 		 * Determine if a relation (on a different RelationalModel) is the reverse
   732 		 * Determine if a relation (on a different RelationalModel) is the reverse
   629 		 * relation of the current one.
   733 		 * relation of the current one.
   642 		 * @return {Backbone.Relation[]}
   746 		 * @return {Backbone.Relation[]}
   643 		 */
   747 		 */
   644 		getReverseRelations: function( model ) {
   748 		getReverseRelations: function( model ) {
   645 			var reverseRelations = [];
   749 			var reverseRelations = [];
   646 			// Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
   750 			// Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
   647 			var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] );
   751 			var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] ),
   648 			_.each( models || [], function( related ) {
   752 				relations = null,
   649 				_.each( related.getRelations() || [], function( relation ) {
   753 				relation = null;
   650 						if ( this._isReverseRelation( relation ) ) {
   754 
   651 							reverseRelations.push( relation );
   755 			for( var i = 0; i < ( models || [] ).length; i++ ) {
   652 						}
   756 				relations = models[ i ].getRelations() || [];
   653 					}, this );
   757 
   654 			}, this );
   758 				for( var j = 0; j < relations.length; j++ ) {
   655 			
   759 					relation = relations[ j ];
       
   760 
       
   761 					if ( this._isReverseRelation( relation ) ) {
       
   762 						reverseRelations.push( relation );
       
   763 					}
       
   764 				}
       
   765 			}
       
   766 
   656 			return reverseRelations;
   767 			return reverseRelations;
   657 		},
   768 		},
   658 
   769 
   659 		/**
   770 		/**
   660 		 * When `this.instance` is destroyed, cleanup our relations.
   771 		 * When `this.instance` is destroyed, cleanup our relations.
   667 				this.setRelated( null );
   778 				this.setRelated( null );
   668 			}
   779 			}
   669 			else if ( this instanceof Backbone.HasMany ) {
   780 			else if ( this instanceof Backbone.HasMany ) {
   670 				this.setRelated( this._prepareCollection() );
   781 				this.setRelated( this._prepareCollection() );
   671 			}
   782 			}
   672 			
   783 
   673 			_.each( this.getReverseRelations(), function( relation ) {
   784 			_.each( this.getReverseRelations(), function( relation ) {
   674 				relation.removeRelated( this.instance );
   785 				relation.removeRelated( this.instance );
   675 			}, this );
   786 			}, this );
   676 		}
   787 		}
   677 	});
   788 	});
   710 				var opts = _.defaults( { create: this.options.createModels }, options );
   821 				var opts = _.defaults( { create: this.options.createModels }, options );
   711 				related = this.relatedModel.findOrCreate( this.keyContents, opts );
   822 				related = this.relatedModel.findOrCreate( this.keyContents, opts );
   712 			}
   823 			}
   713 
   824 
   714 			// Nullify `keyId` if we have a related model; in case it was already part of the relation
   825 			// Nullify `keyId` if we have a related model; in case it was already part of the relation
   715 			if ( this.related ) {
   826 			if ( related ) {
   716 				this.keyId = null;
   827 				this.keyId = null;
   717 			}
   828 			}
   718 
   829 
   719 			return related;
   830 			return related;
   720 		},
   831 		},
   737 			if ( this.isLocked() ) {
   848 			if ( this.isLocked() ) {
   738 				return;
   849 				return;
   739 			}
   850 			}
   740 			this.acquire();
   851 			this.acquire();
   741 			options = options ? _.clone( options ) : {};
   852 			options = options ? _.clone( options ) : {};
   742 			
   853 
   743 			// 'options.__related' is set by 'addRelated'/'removeRelated'. If it is set, the change
   854 			// 'options.__related' is set by 'addRelated'/'removeRelated'. If it is set, the change
   744 			// is the result of a call from a relation. If it's not, the change is the result of 
   855 			// is the result of a call from a relation. If it's not, the change is the result of
   745 			// a 'set' call on this.instance.
   856 			// a 'set' call on this.instance.
   746 			var changed = _.isUndefined( options.__related ),
   857 			var changed = _.isUndefined( options.__related ),
   747 				oldRelated = changed ? this.related : options.__related;
   858 				oldRelated = changed ? this.related : options.__related;
   748 			
   859 
   749 			if ( changed ) {
   860 			if ( changed ) {
   750 				this.setKeyContents( attr );
   861 				this.setKeyContents( attr );
   751 				var related = this.findRelated( options );
   862 				var related = this.findRelated( options );
   752 				this.setRelated( related );
   863 				this.setRelated( related );
   753 			}
   864 			}
   754 			
   865 
   755 			// Notify old 'related' object of the terminated relation
   866 			// Notify old 'related' object of the terminated relation
   756 			if ( oldRelated && this.related !== oldRelated ) {
   867 			if ( oldRelated && this.related !== oldRelated ) {
   757 				_.each( this.getReverseRelations( oldRelated ), function( relation ) {
   868 				_.each( this.getReverseRelations( oldRelated ), function( relation ) {
   758 					relation.removeRelated( this.instance, null, options );
   869 					relation.removeRelated( this.instance, null, options );
   759 				}, this );
   870 				}, this );
   763 			// that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
   874 			// that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
   764 			// In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
   875 			// In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
   765 			_.each( this.getReverseRelations(), function( relation ) {
   876 			_.each( this.getReverseRelations(), function( relation ) {
   766 				relation.addRelated( this.instance, options );
   877 				relation.addRelated( this.instance, options );
   767 			}, this );
   878 			}, this );
   768 			
   879 
   769 			// Fire the 'change:<key>' event if 'related' was updated
   880 			// Fire the 'change:<key>' event if 'related' was updated
   770 			if ( !options.silent && this.related !== oldRelated ) {
   881 			if ( !options.silent && this.related !== oldRelated ) {
   771 				var dit = this;
   882 				var dit = this;
   772 				this.changed = true;
   883 				this.changed = true;
   773 				Backbone.Relational.eventQueue.add( function() {
   884 				Backbone.Relational.eventQueue.add( function() {
   803 
   914 
   804 		removeRelated: function( model, coll, options ) {
   915 		removeRelated: function( model, coll, options ) {
   805 			if ( !this.related ) {
   916 			if ( !this.related ) {
   806 				return;
   917 				return;
   807 			}
   918 			}
   808 			
   919 
   809 			if ( model === this.related ) {
   920 			if ( model === this.related ) {
   810 				var oldRelated = this.related || null;
   921 				var oldRelated = this.related || null;
   811 				this.setRelated( null );
   922 				this.setRelated( null );
   812 				this.onChange( this.instance, model, _.defaults( { __related: oldRelated }, options ) );
   923 				this.onChange( this.instance, model, _.defaults( { __related: oldRelated }, options ) );
   813 			}
   924 			}
   824 			collectionOptions: {}
   935 			collectionOptions: {}
   825 		},
   936 		},
   826 
   937 
   827 		initialize: function( opts ) {
   938 		initialize: function( opts ) {
   828 			this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
   939 			this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange );
   829 			
   940 
   830 			// Handle a custom 'collectionType'
   941 			// Handle a custom 'collectionType'
   831 			this.collectionType = this.options.collectionType;
   942 			this.collectionType = this.options.collectionType;
       
   943 			if ( _.isFunction( this.collectionType ) && this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
       
   944 				this.collectionType = _.result( this, 'collectionType' );
       
   945 			}
   832 			if ( _.isString( this.collectionType ) ) {
   946 			if ( _.isString( this.collectionType ) ) {
   833 				this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
   947 				this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
   834 			}
   948 			}
   835 			if ( this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
   949 			if ( this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
   836 				throw new Error( '`collectionType` must inherit from Backbone.Collection' );
   950 				throw new Error( '`collectionType` must inherit from Backbone.Collection' );
   857 
   971 
   858 				collection = new this.collectionType( null, options );
   972 				collection = new this.collectionType( null, options );
   859 			}
   973 			}
   860 
   974 
   861 			collection.model = this.relatedModel;
   975 			collection.model = this.relatedModel;
   862 			
   976 
   863 			if ( this.options.collectionKey ) {
   977 			if ( this.options.collectionKey ) {
   864 				var key = this.options.collectionKey === true ? this.options.reverseRelation.key : this.options.collectionKey;
   978 				var key = this.options.collectionKey === true ? this.options.reverseRelation.key : this.options.collectionKey;
   865 				
   979 
   866 				if ( collection[ key ] && collection[ key ] !== this.instance ) {
   980 				if ( collection[ key ] && collection[ key ] !== this.instance ) {
   867 					if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
   981 					if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
   868 						console.warn( 'Relation=%o; collectionKey=%s already exists on collection=%o', this, key, this.options.collectionKey );
   982 						console.warn( 'Relation=%o; collectionKey=%s already exists on collection=%o', this, key, this.options.collectionKey );
   869 					}
   983 					}
   870 				}
   984 				}
   874 			}
   988 			}
   875 
   989 
   876 			this.listenTo( collection, 'relational:add', this.handleAddition )
   990 			this.listenTo( collection, 'relational:add', this.handleAddition )
   877 				.listenTo( collection, 'relational:remove', this.handleRemoval )
   991 				.listenTo( collection, 'relational:remove', this.handleRemoval )
   878 				.listenTo( collection, 'relational:reset', this.handleReset );
   992 				.listenTo( collection, 'relational:reset', this.handleReset );
   879 			
   993 
   880 			return collection;
   994 			return collection;
   881 		},
   995 		},
   882 
   996 
   883 		/**
   997 		/**
   884 		 * Find related Models.
   998 		 * Find related Models.
   899 			// Re-use the current 'this.related' if it is a Backbone.Collection; otherwise, create a new collection.
  1013 			// Re-use the current 'this.related' if it is a Backbone.Collection; otherwise, create a new collection.
   900 			else {
  1014 			else {
   901 				var toAdd = [];
  1015 				var toAdd = [];
   902 
  1016 
   903 				_.each( this.keyContents, function( attributes ) {
  1017 				_.each( this.keyContents, function( attributes ) {
       
  1018 					var model = null;
       
  1019 
   904 					if ( attributes instanceof this.relatedModel ) {
  1020 					if ( attributes instanceof this.relatedModel ) {
   905 						var model = attributes;
  1021 						model = attributes;
   906 					}
  1022 					}
   907 					else {
  1023 					else {
   908 						// If `merge` is true, update models here, instead of during update.
  1024 						// If `merge` is true, update models here, instead of during update.
   909 						model = this.relatedModel.findOrCreate( attributes,
  1025 						model = ( _.isObject( attributes ) && options.parse && this.relatedModel.prototype.parse ) ?
   910 							_.extend( { merge: true }, options, { create: this.options.createModels } )
  1026 							this.relatedModel.prototype.parse( _.clone( attributes ), options ) : attributes;
   911 						);
       
   912 					}
  1027 					}
   913 
  1028 
   914 					model && toAdd.push( model );
  1029 					model && toAdd.push( model );
   915 				}, this );
  1030 				}, this );
   916 
  1031 
   919 				}
  1034 				}
   920 				else {
  1035 				else {
   921 					related = this._prepareCollection();
  1036 					related = this._prepareCollection();
   922 				}
  1037 				}
   923 
  1038 
   924 				// By now, both `merge` and `parse` will already have been executed for models if they were specified.
  1039 				// By now, `parse` will already have been executed just above for models if specified.
   925 				// Disable them to prevent additional calls.
  1040 				// Disable to prevent additional calls.
   926 				related.set( toAdd, _.defaults( { merge: false, parse: false }, options ) );
  1041 				related.set( toAdd, _.defaults( { parse: false }, options ) );
   927 			}
  1042 			}
   928 
  1043 
   929 			// Remove entries from `keyIds` that were already part of the relation (and are thus 'unchanged')
  1044 			// Remove entries from `keyIds` that were already part of the relation (and are thus 'unchanged')
   930 			this.keyIds = _.difference( this.keyIds, _.pluck( related.models, 'id' ) );
  1045 			this.keyIds = _.difference( this.keyIds, _.pluck( related.models, 'id' ) );
   931 
  1046 
   978 		},
  1093 		},
   979 
  1094 
   980 		/**
  1095 		/**
   981 		 * When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations.
  1096 		 * When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations.
   982 		 * (should be 'HasOne', must set 'this.instance' as their related).
  1097 		 * (should be 'HasOne', must set 'this.instance' as their related).
   983 		*/
  1098 		 */
   984 		handleAddition: function( model, coll, options ) {
  1099 		handleAddition: function( model, coll, options ) {
   985 			//console.debug('handleAddition called; args=%o', arguments);
  1100 			//console.debug('handleAddition called; args=%o', arguments);
   986 			options = options ? _.clone( options ) : {};
  1101 			options = options ? _.clone( options ) : {};
   987 			this.changed = true;
  1102 			this.changed = true;
   988 			
  1103 
   989 			_.each( this.getReverseRelations( model ), function( relation ) {
  1104 			_.each( this.getReverseRelations( model ), function( relation ) {
   990 				relation.addRelated( this.instance, options );
  1105 				relation.addRelated( this.instance, options );
   991 			}, this );
  1106 			}, this );
   992 
  1107 
   993 			// Only trigger 'add' once the newly added model is initialized (so, has its relations set up)
  1108 			// Only trigger 'add' once the newly added model is initialized (so, has its relations set up)
  1003 		 */
  1118 		 */
  1004 		handleRemoval: function( model, coll, options ) {
  1119 		handleRemoval: function( model, coll, options ) {
  1005 			//console.debug('handleRemoval called; args=%o', arguments);
  1120 			//console.debug('handleRemoval called; args=%o', arguments);
  1006 			options = options ? _.clone( options ) : {};
  1121 			options = options ? _.clone( options ) : {};
  1007 			this.changed = true;
  1122 			this.changed = true;
  1008 			
  1123 
  1009 			_.each( this.getReverseRelations( model ), function( relation ) {
  1124 			_.each( this.getReverseRelations( model ), function( relation ) {
  1010 				relation.removeRelated( this.instance, null, options );
  1125 				relation.removeRelated( this.instance, null, options );
  1011 			}, this );
  1126 			}, this );
  1012 			
  1127 
  1013 			var dit = this;
  1128 			var dit = this;
  1014 			!options.silent && Backbone.Relational.eventQueue.add( function() {
  1129 			!options.silent && Backbone.Relational.eventQueue.add( function() {
  1015 				 dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options );
  1130 				dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options );
  1016 			});
  1131 			});
  1017 		},
  1132 		},
  1018 
  1133 
  1019 		handleReset: function( coll, options ) {
  1134 		handleReset: function( coll, options ) {
  1020 			var dit = this;
  1135 			var dit = this;
  1062 		relations: null, // Relation descriptions on the prototype
  1177 		relations: null, // Relation descriptions on the prototype
  1063 		_relations: null, // Relation instances
  1178 		_relations: null, // Relation instances
  1064 		_isInitialized: false,
  1179 		_isInitialized: false,
  1065 		_deferProcessing: false,
  1180 		_deferProcessing: false,
  1066 		_queue: null,
  1181 		_queue: null,
       
  1182 		_attributeChangeFired: false, // Keeps track of `change` event firing under some conditions (like nested `set`s)
  1067 
  1183 
  1068 		subModelTypeAttribute: 'type',
  1184 		subModelTypeAttribute: 'type',
  1069 		subModelTypes: null,
  1185 		subModelTypes: null,
  1070 
  1186 
  1071 		constructor: function( attributes, options ) {
  1187 		constructor: function( attributes, options ) {
  1073 			// Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
  1189 			// Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany'
  1074 			// collection events only after the model is really fully set up.
  1190 			// collection events only after the model is really fully set up.
  1075 			// Example: event for "p.on( 'add:jobs' )" -> "p.get('jobs').add( { company: c.id, person: p.id } )".
  1191 			// Example: event for "p.on( 'add:jobs' )" -> "p.get('jobs').add( { company: c.id, person: p.id } )".
  1076 			if ( options && options.collection ) {
  1192 			if ( options && options.collection ) {
  1077 				var dit = this,
  1193 				var dit = this,
  1078 					collection = this.collection =  options.collection;
  1194 					collection = this.collection = options.collection;
  1079 
  1195 
  1080 				// Prevent `collection` from cascading down to nested models; they shouldn't go into this `if` clause.
  1196 				// Prevent `collection` from cascading down to nested models; they shouldn't go into this `if` clause.
  1081 				delete options.collection;
  1197 				delete options.collection;
  1082 
  1198 
  1083 				this._deferProcessing = true;
  1199 				this._deferProcessing = true;
  1096 					processQueue( dit );
  1212 					processQueue( dit );
  1097 				});
  1213 				});
  1098 			}
  1214 			}
  1099 
  1215 
  1100 			Backbone.Relational.store.processOrphanRelations();
  1216 			Backbone.Relational.store.processOrphanRelations();
  1101 			
  1217 			Backbone.Relational.store.listenTo( this, 'relational:unregister', Backbone.Relational.store.unregister );
       
  1218 
  1102 			this._queue = new Backbone.BlockingQueue();
  1219 			this._queue = new Backbone.BlockingQueue();
  1103 			this._queue.block();
  1220 			this._queue.block();
  1104 			Backbone.Relational.eventQueue.block();
  1221 			Backbone.Relational.eventQueue.block();
  1105 
  1222 
  1106 			try {
  1223 			try {
  1118 		trigger: function( eventName ) {
  1235 		trigger: function( eventName ) {
  1119 			if ( eventName.length > 5 && eventName.indexOf( 'change' ) === 0 ) {
  1236 			if ( eventName.length > 5 && eventName.indexOf( 'change' ) === 0 ) {
  1120 				var dit = this,
  1237 				var dit = this,
  1121 					args = arguments;
  1238 					args = arguments;
  1122 
  1239 
  1123 				Backbone.Relational.eventQueue.add( function() {
  1240 				if ( !Backbone.Relational.eventQueue.isBlocked() ) {
  1124 					if ( !dit._isInitialized ) {
  1241 					// If we're not in a more complicated nested scenario, fire the change event right away
  1125 						return;
  1242 					Backbone.Model.prototype.trigger.apply( dit, args );
  1126 					}
  1243 				}
  1127 
  1244 				else {
  1128 					// Determine if the `change` event is still valid, now that all relations are populated
  1245 					Backbone.Relational.eventQueue.add( function() {
  1129 					var changed = true;
  1246 						// Determine if the `change` event is still valid, now that all relations are populated
  1130 					if ( eventName === 'change' ) {
  1247 						var changed = true;
  1131 						changed = dit.hasChanged();
  1248 						if ( eventName === 'change' ) {
  1132 					}
  1249 							// `hasChanged` may have gotten reset by nested calls to `set`.
  1133 					else {
  1250 							changed = dit.hasChanged() || dit._attributeChangeFired;
  1134 						var attr = eventName.slice( 7 ),
  1251 							dit._attributeChangeFired = false;
  1135 							rel = dit.getRelation( attr );
  1252 						}
  1136 
  1253 						else {
  1137 						if ( rel ) {
  1254 							var attr = eventName.slice( 7 ),
  1138 							// If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`.
  1255 								rel = dit.getRelation( attr );
  1139 							// These take precedence over `change:attr` events triggered by `Model.set`.
  1256 
  1140 							// The relation set a fourth attribute to `true`. If this attribute is present,
  1257 							if ( rel ) {
  1141 							// continue triggering this event; otherwise, it's from `Model.set` and should be stopped.
  1258 								// If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`.
  1142 							changed = ( args[ 4 ] === true );
  1259 								// These take precedence over `change:attr` events triggered by `Model.set`.
  1143 
  1260 								// The relation sets a fourth attribute to `true`. If this attribute is present,
  1144 							// If this event was triggered by a relation, set the right value in `this.changed`
  1261 								// continue triggering this event; otherwise, it's from `Model.set` and should be stopped.
  1145 							// (a Collection or Model instead of raw data).
  1262 								changed = ( args[ 4 ] === true );
  1146 							if ( changed ) {
  1263 
  1147 								dit.changed[ attr ] = args[ 2 ];
  1264 								// If this event was triggered by a relation, set the right value in `this.changed`
       
  1265 								// (a Collection or Model instead of raw data).
       
  1266 								if ( changed ) {
       
  1267 									dit.changed[ attr ] = args[ 2 ];
       
  1268 								}
       
  1269 								// Otherwise, this event is from `Model.set`. If the relation doesn't report a change,
       
  1270 								// remove attr from `dit.changed` so `hasChanged` doesn't take it into account.
       
  1271 								else if ( !rel.changed ) {
       
  1272 									delete dit.changed[ attr ];
       
  1273 								}
  1148 							}
  1274 							}
  1149 							// Otherwise, this event is from `Model.set`. If the relation doesn't report a change,
  1275 							else if ( changed ) {
  1150 							// remove attr from `dit.changed` so `hasChanged` doesn't take it into account.
  1276 								dit._attributeChangeFired = true;
  1151 							else if ( !rel.changed ) {
       
  1152 								delete dit.changed[ attr ];
       
  1153 							}
  1277 							}
  1154 						}
  1278 						}
  1155 					}
  1279 
  1156 
  1280 						changed && Backbone.Model.prototype.trigger.apply( dit, args );
  1157 					changed && Backbone.Model.prototype.trigger.apply( dit, args );
  1281 					});
  1158 				});
  1282 				}
       
  1283 			}
       
  1284 			else if ( eventName === 'destroy' ) {
       
  1285 				Backbone.Model.prototype.trigger.apply( this, arguments );
       
  1286 				Backbone.Relational.store.unregister( this );
  1159 			}
  1287 			}
  1160 			else {
  1288 			else {
  1161 				Backbone.Model.prototype.trigger.apply( this, arguments );
  1289 				Backbone.Model.prototype.trigger.apply( this, arguments );
  1162 			}
  1290 			}
  1163 
  1291 
  1181 			this.processQueue();
  1309 			this.processQueue();
  1182 		},
  1310 		},
  1183 
  1311 
  1184 		/**
  1312 		/**
  1185 		 * When new values are set, notify this model's relations (also if options.silent is set).
  1313 		 * When new values are set, notify this model's relations (also if options.silent is set).
  1186 		 * (Relation.setRelated locks this model before calling 'set' on it to prevent loops)
  1314 		 * (called from `set`; Relation.setRelated locks this model before calling 'set' on it to prevent loops)
  1187 		 */
  1315 		 * @param {Object} [changedAttrs]
  1188 		updateRelations: function( options ) {
  1316 		 * @param {Object} [options]
       
  1317 		 */
       
  1318 		updateRelations: function( changedAttrs, options ) {
  1189 			if ( this._isInitialized && !this.isLocked() ) {
  1319 			if ( this._isInitialized && !this.isLocked() ) {
  1190 				_.each( this._relations, function( rel ) {
  1320 				_.each( this._relations, function( rel ) {
  1191 					// Update from data in `rel.keySource` if set, or `rel.key` otherwise
  1321 					if ( !changedAttrs || ( rel.keySource in changedAttrs || rel.key in changedAttrs ) ) {
  1192 					var val = this.attributes[ rel.keySource ] || this.attributes[ rel.key ];
  1322 						// Fetch data in `rel.keySource` if data got set in there, or `rel.key` otherwise
  1193 					if ( rel.related !== val ) {
  1323 						var value = this.attributes[ rel.keySource ] || this.attributes[ rel.key ],
  1194 						this.trigger( 'relational:change:' + rel.key, this, val, options || {} );
  1324 							attr = changedAttrs && ( changedAttrs[ rel.keySource ] || changedAttrs[ rel.key ] );
       
  1325 
       
  1326 						// Update a relation if its value differs from this model's attributes, or it's been explicitly nullified.
       
  1327 						// Which can also happen before the originally intended related model has been found (`val` is null).
       
  1328 						if ( rel.related !== value || ( value === null && attr === null ) ) {
       
  1329 							this.trigger( 'relational:change:' + rel.key, this, value, options || {} );
       
  1330 						}
       
  1331 					}
       
  1332 
       
  1333 					// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
       
  1334 					if ( rel.keySource !== rel.key ) {
       
  1335 						delete this.attributes[ rel.keySource ];
  1195 					}
  1336 					}
  1196 				}, this );
  1337 				}, this );
  1197 			}
  1338 			}
  1198 		},
  1339 		},
  1199 
  1340 
  1213 			}
  1354 			}
  1214 		},
  1355 		},
  1215 
  1356 
  1216 		/**
  1357 		/**
  1217 		 * Get a specific relation.
  1358 		 * Get a specific relation.
  1218 		 * @param key {string} The relation key to look for.
  1359 		 * @param {string} attr The relation key to look for.
  1219 		 * @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'key', or null.
  1360 		 * @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'attr', or null.
  1220 		 */
  1361 		 */
  1221 		getRelation: function( key ) {
  1362 		getRelation: function( attr ) {
  1222 			return this._relations[ key ];
  1363 			return this._relations[ attr ];
  1223 		},
  1364 		},
  1224 
  1365 
  1225 		/**
  1366 		/**
  1226 		 * Get all of the created relations.
  1367 		 * Get all of the created relations.
  1227 		 * @return {Backbone.Relation[]}
  1368 		 * @return {Backbone.Relation[]}
  1228 		 */
  1369 		 */
  1229 		getRelations: function() {
  1370 		getRelations: function() {
  1230 			return _.values( this._relations );
  1371 			return _.values( this._relations );
  1231 		},
  1372 		},
  1232 
  1373 
  1233 		/**
  1374 
  1234 		 * Retrieve related objects.
  1375 		/**
  1235 		 * @param key {string} The relation key to fetch models for.
  1376 		 * Get a list of ids that will be fetched on a call to `getAsync`.
  1236 		 * @param [options] {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
  1377 		 * @param {string|Backbone.Relation} attr The relation key to fetch models for.
  1237 		 * @param [refresh=false] {boolean} Fetch existing models from the server as well (in order to update them).
  1378 		 * @param [refresh=false] Add ids for models that are already in the relation, refreshing them?
  1238 		 * @return {jQuery.when[]} An array of request objects
  1379 		 * @return {Array} An array of ids that need to be fetched.
  1239 		 */
  1380 		 */
  1240 		fetchRelated: function( key, options, refresh ) {
  1381 		getIdsToFetch: function( attr, refresh ) {
  1241 			// Set default `options` for fetch
  1382 			var rel = attr instanceof Backbone.Relation ? attr : this.getRelation( attr ),
  1242 			options = _.extend( { update: true, remove: false }, options );
  1383 				ids = rel ? ( rel.keyIds && rel.keyIds.slice( 0 ) ) || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) : [];
  1243 
       
  1244 			var setUrl,
       
  1245 				requests = [],
       
  1246 				rel = this.getRelation( key ),
       
  1247 				idsToFetch = rel && ( rel.keyIds || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) );
       
  1248 
  1384 
  1249 			// On `refresh`, add the ids for current models in the relation to `idsToFetch`
  1385 			// On `refresh`, add the ids for current models in the relation to `idsToFetch`
  1250 			if ( refresh ) {
  1386 			if ( refresh ) {
  1251 				var models = rel.related instanceof Backbone.Collection ? rel.related.models : [ rel.related ];
  1387 				var models = rel.related && ( rel.related.models || [ rel.related ] );
  1252 				_.each( models, function( model ) {
  1388 				_.each( models, function( model ) {
  1253 					if ( model.id || model.id === 0 ) {
  1389 					if ( model.id || model.id === 0 ) {
  1254 						idsToFetch.push( model.id );
  1390 						ids.push( model.id );
  1255 					}
  1391 					}
  1256 				});
  1392 				});
  1257 			}
  1393 			}
  1258 
  1394 
       
  1395 			return ids;
       
  1396 		},
       
  1397 
       
  1398 		/**
       
  1399 		 * Get related objects. Returns a single promise, which can either resolve immediately (if the related model[s])
       
  1400 		 * are already present locally, or after fetching the contents of the requested attribute.
       
  1401 		 * @param {string} attr The relation key to fetch models for.
       
  1402 		 * @param {Object} [options] Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
       
  1403 		 * @param {Boolean} [options.refresh=false] Fetch existing models from the server as well (in order to update them).
       
  1404 		 * @return {jQuery.Deferred} A jQuery promise object. When resolved, its `done` callback will be called with
       
  1405 		 *  contents of `attr`.
       
  1406 		 */
       
  1407 		getAsync: function( attr, options ) {
       
  1408 			// Set default `options` for fetch
       
  1409 			options = _.extend( { add: true, remove: false, refresh: false }, options );
       
  1410 
       
  1411 			var dit = this,
       
  1412 				requests = [],
       
  1413 				rel = this.getRelation( attr ),
       
  1414 				idsToFetch = rel && this.getIdsToFetch( rel, options.refresh ),
       
  1415 				coll = rel.related instanceof Backbone.Collection ? rel.related : rel.relatedCollection;
       
  1416 
  1259 			if ( idsToFetch && idsToFetch.length ) {
  1417 			if ( idsToFetch && idsToFetch.length ) {
  1260 				// Find (or create) a model for each one that is to be fetched
  1418 				var models = [],
  1261 				var created = [],
  1419 					createdModels = [],
  1262 					models = _.map( idsToFetch, function( id ) {
  1420 					setUrl,
  1263 						var model = Backbone.Relational.store.find( rel.relatedModel, id );
  1421 					createModels = function() {
  1264 						
  1422 						// Find (or create) a model for each one that is to be fetched
  1265 						if ( !model ) {
  1423 						models = _.map( idsToFetch, function( id ) {
  1266 							var attrs = {};
  1424 							var model = rel.relatedModel.findModel( id );
  1267 							attrs[ rel.relatedModel.prototype.idAttribute ] = id;
  1425 
  1268 							model = rel.relatedModel.findOrCreate( attrs, options );
  1426 							if ( !model ) {
  1269 							created.push( model );
  1427 								var attrs = {};
  1270 						}
  1428 								attrs[ rel.relatedModel.prototype.idAttribute ] = id;
  1271 
  1429 								model = rel.relatedModel.findOrCreate( attrs, options );
  1272 						return model;
  1430 								createdModels.push( model );
  1273 					}, this );
  1431 							}
  1274 				
  1432 
       
  1433 							return model;
       
  1434 						}, this );
       
  1435 					};
       
  1436 
  1275 				// Try if the 'collection' can provide a url to fetch a set of models in one request.
  1437 				// Try if the 'collection' can provide a url to fetch a set of models in one request.
  1276 				if ( rel.related instanceof Backbone.Collection && _.isFunction( rel.related.url ) ) {
  1438 				// This assumes that when 'Backbone.Collection.url' is a function, it can handle building of set urls.
  1277 					setUrl = rel.related.url( models );
       
  1278 				}
       
  1279 
       
  1280 				// An assumption is that when 'Backbone.Collection.url' is a function, it can handle building of set urls.
       
  1281 				// To make sure it can, test if the url we got by supplying a list of models to fetch is different from
  1439 				// To make sure it can, test if the url we got by supplying a list of models to fetch is different from
  1282 				// the one supplied for the default fetch action (without args to 'url').
  1440 				// the one supplied for the default fetch action (without args to 'url').
  1283 				if ( setUrl && setUrl !== rel.related.url() ) {
  1441 				if ( coll instanceof Backbone.Collection && _.isFunction( coll.url ) ) {
       
  1442 					var defaultUrl = coll.url();
       
  1443 					setUrl = coll.url( idsToFetch );
       
  1444 
       
  1445 					if ( setUrl === defaultUrl ) {
       
  1446 						createModels();
       
  1447 						setUrl = coll.url( models );
       
  1448 
       
  1449 						if ( setUrl === defaultUrl ) {
       
  1450 							setUrl = null;
       
  1451 						}
       
  1452 					}
       
  1453 				}
       
  1454 
       
  1455 				if ( setUrl ) {
       
  1456 					// Do a single request to fetch all models
  1284 					var opts = _.defaults(
  1457 					var opts = _.defaults(
  1285 						{
  1458 						{
  1286 							error: function() {
  1459 							error: function() {
  1287 								var args = arguments;
  1460 								_.each( createdModels, function( model ) {
  1288 								_.each( created, function( model ) {
       
  1289 									model.trigger( 'destroy', model, model.collection, options );
  1461 									model.trigger( 'destroy', model, model.collection, options );
  1290 									options.error && options.error.apply( model, args );
       
  1291 								});
  1462 								});
       
  1463 								
       
  1464 								options.error && options.error.apply( models, arguments );
  1292 							},
  1465 							},
  1293 							url: setUrl
  1466 							url: setUrl
  1294 						},
  1467 						},
  1295 						options
  1468 						options
  1296 					);
  1469 					);
  1297 
  1470 
  1298 					requests = [ rel.related.fetch( opts ) ];
  1471 					requests = [ coll.fetch( opts ) ];
  1299 				}
  1472 				}
  1300 				else {
  1473 				else {
       
  1474 					// Make a request per model to fetch
       
  1475 					if  ( !models.length ) {
       
  1476 						createModels();
       
  1477 					}
       
  1478 
  1301 					requests = _.map( models, function( model ) {
  1479 					requests = _.map( models, function( model ) {
  1302 						var opts = _.defaults(
  1480 						var opts = _.defaults(
  1303 							{
  1481 							{
  1304 								error: function() {
  1482 								error: function() {
  1305 									if ( _.contains( created, model ) ) {
  1483 									if ( _.contains( createdModels, model ) ) {
  1306 										model.trigger( 'destroy', model, model.collection, options );
  1484 										model.trigger( 'destroy', model, model.collection, options );
  1307 										options.error && options.error.apply( model, arguments );
       
  1308 									}
  1485 									}
       
  1486 									options.error && options.error.apply( models, arguments );
  1309 								}
  1487 								}
  1310 							},
  1488 							},
  1311 							options
  1489 							options
  1312 						);
  1490 						);
  1313 						return model.fetch( opts );
  1491 						return model.fetch( opts );
  1314 					}, this );
  1492 					}, this );
  1315 				}
  1493 				}
  1316 			}
  1494 			}
  1317 			
  1495 
  1318 			return requests;
  1496 			return this.deferArray(requests).then(
  1319 		},
  1497 				function() {
  1320 
  1498 					return Backbone.Model.prototype.get.call( dit, attr );
  1321 		get: function( attr ) {
  1499 				}
  1322 			var originalResult = Backbone.Model.prototype.get.call( this, attr );
  1500 			);
  1323 
  1501 		},
  1324 			// Use `originalResult` get if dotNotation not enabled or not required because no dot is in `attr`
  1502 		
  1325 			if ( !this.dotNotation || attr.indexOf( '.' ) === -1 ) {
  1503 		deferArray: function(deferArray) {
  1326 				return originalResult;
  1504 			return Backbone.$.when.apply(null, deferArray);
  1327 			}
       
  1328 
       
  1329 			// Go through all splits and return the final result
       
  1330 			var splits = attr.split( '.' );
       
  1331 			var result = _.reduce(splits, function( model, split ) {
       
  1332 				if ( !( model instanceof Backbone.Model ) ) {
       
  1333 					throw new Error( 'Attribute must be an instanceof Backbone.Model. Is: ' + model + ', currentSplit: ' + split );
       
  1334 				}
       
  1335 
       
  1336 				return Backbone.Model.prototype.get.call( model, split );
       
  1337 			}, this );
       
  1338 
       
  1339 			if ( originalResult !== undefined && result !== undefined ) {
       
  1340 				throw new Error( "Ambiguous result for '" + attr + "'. direct result: " + originalResult + ", dotNotation: " + result );
       
  1341 			}
       
  1342 
       
  1343 			return originalResult || result;
       
  1344 		},
  1505 		},
  1345 
  1506 
  1346 		set: function( key, value, options ) {
  1507 		set: function( key, value, options ) {
  1347 			Backbone.Relational.eventQueue.block();
  1508 			Backbone.Relational.eventQueue.block();
  1348 
  1509 
  1349 			// Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
  1510 			// Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
  1350 			var attributes;
  1511 			var attributes,
       
  1512 				result;
       
  1513 
  1351 			if ( _.isObject( key ) || key == null ) {
  1514 			if ( _.isObject( key ) || key == null ) {
  1352 				attributes = key;
  1515 				attributes = key;
  1353 				options = value;
  1516 				options = value;
  1354 			}
  1517 			}
  1355 			else {
  1518 			else {
  1362 					newId = attributes && this.idAttribute in attributes && attributes[ this.idAttribute ];
  1525 					newId = attributes && this.idAttribute in attributes && attributes[ this.idAttribute ];
  1363 
  1526 
  1364 				// Check if we're not setting a duplicate id before actually calling `set`.
  1527 				// Check if we're not setting a duplicate id before actually calling `set`.
  1365 				Backbone.Relational.store.checkId( this, newId );
  1528 				Backbone.Relational.store.checkId( this, newId );
  1366 
  1529 
  1367 				var result = Backbone.Model.prototype.set.apply( this, arguments );
  1530 				result = Backbone.Model.prototype.set.apply( this, arguments );
  1368 
  1531 
  1369 				// Ideal place to set up relations, if this is the first time we're here for this model
  1532 				// Ideal place to set up relations, if this is the first time we're here for this model
  1370 				if ( !this._isInitialized && !this.isLocked() ) {
  1533 				if ( !this._isInitialized && !this.isLocked() ) {
  1371 					this.constructor.initializeModelHierarchy();
  1534 					this.constructor.initializeModelHierarchy();
  1372 					Backbone.Relational.store.register( this );
  1535 
       
  1536 					// Only register models that have an id. A model will be registered when/if it gets an id later on.
       
  1537 					if ( newId || newId === 0 ) {
       
  1538 						Backbone.Relational.store.register( this );
       
  1539 					}
       
  1540 
  1373 					this.initializeRelations( options );
  1541 					this.initializeRelations( options );
  1374 				}
  1542 				}
  1375 				// The store should know about an `id` update asap
  1543 				// The store should know about an `id` update asap
  1376 				else if ( newId && newId !== id ) {
  1544 				else if ( newId && newId !== id ) {
  1377 					Backbone.Relational.store.update( this );
  1545 					Backbone.Relational.store.update( this );
  1378 				}
  1546 				}
  1379 
  1547 
  1380 				if ( attributes ) {
  1548 				if ( attributes ) {
  1381 					this.updateRelations( options );
  1549 					this.updateRelations( attributes, options );
  1382 				}
  1550 				}
  1383 			}
  1551 			}
  1384 			finally {
  1552 			finally {
  1385 				// Try to run the global queue holding external events
  1553 				// Try to run the global queue holding external events
  1386 				Backbone.Relational.eventQueue.unblock();
  1554 				Backbone.Relational.eventQueue.unblock();
  1387 			}
  1555 			}
  1388 			
       
  1389 			return result;
       
  1390 		},
       
  1391 
       
  1392 		unset: function( attribute, options ) {
       
  1393 			Backbone.Relational.eventQueue.block();
       
  1394 
       
  1395 			var result = Backbone.Model.prototype.unset.apply( this, arguments );
       
  1396 			this.updateRelations( options );
       
  1397 
       
  1398 			// Try to run the global queue holding external events
       
  1399 			Backbone.Relational.eventQueue.unblock();
       
  1400 
       
  1401 			return result;
       
  1402 		},
       
  1403 
       
  1404 		clear: function( options ) {
       
  1405 			Backbone.Relational.eventQueue.block();
       
  1406 			
       
  1407 			var result = Backbone.Model.prototype.clear.apply( this, arguments );
       
  1408 			this.updateRelations( options );
       
  1409 
       
  1410 			// Try to run the global queue holding external events
       
  1411 			Backbone.Relational.eventQueue.unblock();
       
  1412 
  1556 
  1413 			return result;
  1557 			return result;
  1414 		},
  1558 		},
  1415 
  1559 
  1416 		clone: function() {
  1560 		clone: function() {
  1463 					// Add ids for 'unfound' models if includeInJSON is equal to (only) the relatedModel's `idAttribute`
  1607 					// Add ids for 'unfound' models if includeInJSON is equal to (only) the relatedModel's `idAttribute`
  1464 					if ( includeInJSON === rel.relatedModel.prototype.idAttribute ) {
  1608 					if ( includeInJSON === rel.relatedModel.prototype.idAttribute ) {
  1465 						if ( rel instanceof Backbone.HasMany ) {
  1609 						if ( rel instanceof Backbone.HasMany ) {
  1466 							value = value.concat( rel.keyIds );
  1610 							value = value.concat( rel.keyIds );
  1467 						}
  1611 						}
  1468 						else if  ( rel instanceof Backbone.HasOne ) {
  1612 						else if ( rel instanceof Backbone.HasOne ) {
  1469 							value = value || rel.keyId;
  1613 							value = value || rel.keyId;
       
  1614 
       
  1615 							if ( !value && !_.isObject( rel.keyContents ) ) {
       
  1616 								value = rel.keyContents || null;
       
  1617 							}
  1470 						}
  1618 						}
  1471 					}
  1619 					}
  1472 				}
  1620 				}
  1473 				else if ( _.isArray( includeInJSON ) ) {
  1621 				else if ( _.isArray( includeInJSON ) ) {
  1474 					if ( related instanceof Backbone.Collection ) {
  1622 					if ( related instanceof Backbone.Collection ) {
  1490 				}
  1638 				}
  1491 				else {
  1639 				else {
  1492 					delete json[ rel.key ];
  1640 					delete json[ rel.key ];
  1493 				}
  1641 				}
  1494 
  1642 
       
  1643 				// In case of `wait: true`, Backbone will simply push whatever's passed into `save` into attributes.
       
  1644 				// We'll want to get this information into the JSON, even if it doesn't conform to our normal
       
  1645 				// expectations of what's contained in it (no model/collection for a relation, etc).
       
  1646 				if ( value === null && options && options.wait ) {
       
  1647 					value = related;
       
  1648 				}
       
  1649 
  1495 				if ( includeInJSON ) {
  1650 				if ( includeInJSON ) {
  1496 					json[ rel.keyDestination ] = value;
  1651 					json[ rel.keyDestination ] = value;
  1497 				}
  1652 				}
  1498 
  1653 
  1499 				if ( rel.keyDestination !== rel.key ) {
  1654 				if ( rel.keyDestination !== rel.key ) {
  1500 					delete json[ rel.key ];
  1655 					delete json[ rel.key ];
  1501 				}
  1656 				}
  1502 			});
  1657 			});
  1503 			
  1658 
  1504 			this.release();
  1659 			this.release();
  1505 			return json;
  1660 			return json;
  1506 		}
  1661 		}
  1507 	},
  1662 	},
  1508 	{
  1663 	{
  1510 		 *
  1665 		 *
  1511 		 * @param superModel
  1666 		 * @param superModel
  1512 		 * @returns {Backbone.RelationalModel.constructor}
  1667 		 * @returns {Backbone.RelationalModel.constructor}
  1513 		 */
  1668 		 */
  1514 		setup: function( superModel ) {
  1669 		setup: function( superModel ) {
  1515 			// We don't want to share a relations array with a parent, as this will cause problems with
  1670 			// We don't want to share a relations array with a parent, as this will cause problems with reverse
  1516 			// reverse relations.
  1671 			// relations. Since `relations` may also be a property or function, only use slice if we have an array.
  1517 			this.prototype.relations = ( this.prototype.relations || [] ).slice( 0 );
  1672 			this.prototype.relations = ( this.prototype.relations || [] ).slice( 0 );
  1518 
  1673 
  1519 			this._subModels = {};
  1674 			this._subModels = {};
  1520 			this._superModel = null;
  1675 			this._superModel = null;
  1521 
  1676 
  1531 			// Initialize all reverseRelations that belong to this new model.
  1686 			// Initialize all reverseRelations that belong to this new model.
  1532 			_.each( this.prototype.relations || [], function( rel ) {
  1687 			_.each( this.prototype.relations || [], function( rel ) {
  1533 				if ( !rel.model ) {
  1688 				if ( !rel.model ) {
  1534 					rel.model = this;
  1689 					rel.model = this;
  1535 				}
  1690 				}
  1536 				
  1691 
  1537 				if ( rel.reverseRelation && rel.model === this ) {
  1692 				if ( rel.reverseRelation && rel.model === this ) {
  1538 					var preInitialize = true;
  1693 					var preInitialize = true;
  1539 					if ( _.isString( rel.relatedModel ) ) {
  1694 					if ( _.isString( rel.relatedModel ) ) {
  1540 						/**
  1695 						/**
  1541 						 * The related model might not be defined for two reasons
  1696 						 * The related model might not be defined for two reasons
  1556 					else if ( _.isString( rel.relatedModel ) ) {
  1711 					else if ( _.isString( rel.relatedModel ) ) {
  1557 						Backbone.Relational.store.addOrphanRelation( rel );
  1712 						Backbone.Relational.store.addOrphanRelation( rel );
  1558 					}
  1713 					}
  1559 				}
  1714 				}
  1560 			}, this );
  1715 			}, this );
  1561 			
  1716 
  1562 			return this;
  1717 			return this;
  1563 		},
  1718 		},
  1564 
  1719 
  1565 		/**
  1720 		/**
  1566 		 * Create a 'Backbone.Model' instance based on 'attributes'.
  1721 		 * Create a 'Backbone.Model' instance based on 'attributes'.
  1567 		 * @param {Object} attributes
  1722 		 * @param {Object} attributes
  1568 		 * @param {Object} [options]
  1723 		 * @param {Object} [options]
  1569 		 * @return {Backbone.Model}
  1724 		 * @return {Backbone.Model}
  1570 		 */
  1725 		 */
  1571 		build: function( attributes, options ) {
  1726 		build: function( attributes, options ) {
  1572 			var model = this;
       
  1573 
       
  1574 			// 'build' is a possible entrypoint; it's possible no model hierarchy has been determined yet.
  1727 			// 'build' is a possible entrypoint; it's possible no model hierarchy has been determined yet.
  1575 			this.initializeModelHierarchy();
  1728 			this.initializeModelHierarchy();
  1576 
  1729 
  1577 			// Determine what type of (sub)model should be built if applicable.
  1730 			// Determine what type of (sub)model should be built if applicable.
  1578 			// Lookup the proper subModelType in 'this._subModels'.
  1731 			var model = this._findSubModelType( this, attributes ) || this;
  1579 			if ( this._subModels && this.prototype.subModelTypeAttribute in attributes ) {
  1732 
  1580 				var subModelTypeAttribute = attributes[ this.prototype.subModelTypeAttribute ];
  1733 			return new model( attributes, options );
  1581 				var subModelType = this._subModels[ subModelTypeAttribute ];
  1734 		},
       
  1735 
       
  1736 		/**
       
  1737 		 * Determines what type of (sub)model should be built if applicable.
       
  1738 		 * Looks up the proper subModelType in 'this._subModels', recursing into
       
  1739 		 * types until a match is found.  Returns the applicable 'Backbone.Model'
       
  1740 		 * or null if no match is found.
       
  1741 		 * @param {Backbone.Model} type
       
  1742 		 * @param {Object} attributes
       
  1743 		 * @return {Backbone.Model}
       
  1744 		 */
       
  1745 		_findSubModelType: function( type, attributes ) {
       
  1746 			if ( type._subModels && type.prototype.subModelTypeAttribute in attributes ) {
       
  1747 				var subModelTypeAttribute = attributes[ type.prototype.subModelTypeAttribute ];
       
  1748 				var subModelType = type._subModels[ subModelTypeAttribute ];
  1582 				if ( subModelType ) {
  1749 				if ( subModelType ) {
  1583 					model = subModelType;
  1750 					return subModelType;
  1584 				}
  1751 				}
  1585 			}
  1752 				else {
  1586 			
  1753 					// Recurse into subModelTypes to find a match
  1587 			return new model( attributes, options );
  1754 					for ( subModelTypeAttribute in type._subModels ) {
       
  1755 						subModelType = this._findSubModelType( type._subModels[ subModelTypeAttribute ], attributes );
       
  1756 						if ( subModelType ) {
       
  1757 							return subModelType;
       
  1758 						}
       
  1759 					}
       
  1760 				}
       
  1761 			}
       
  1762 
       
  1763 			return null;
  1588 		},
  1764 		},
  1589 
  1765 
  1590 		/**
  1766 		/**
  1591 		 *
  1767 		 *
  1592 		 */
  1768 		 */
  1593 		initializeModelHierarchy: function() {
  1769 		initializeModelHierarchy: function() {
  1594 			// If we're here for the first time, try to determine if this modelType has a 'superModel'.
  1770 			// Inherit any relations that have been defined in the parent model.
  1595 			if ( _.isUndefined( this._superModel ) || _.isNull( this._superModel ) ) {
  1771 			this.inheritRelations();
  1596 				Backbone.Relational.store.setupSuperModel( this );
  1772 
  1597 
  1773 			// If we came here through 'build' for a model that has 'subModelTypes' then try to initialize the ones that
  1598 				// If a superModel has been found, copy relations from the _superModel if they haven't been
  1774 			// haven't been resolved yet.
  1599 				// inherited automatically (due to a redefinition of 'relations').
  1775 			if ( this.prototype.subModelTypes ) {
  1600 				// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail
  1776 				var resolvedSubModels = _.keys( this._subModels );
  1601 				// the isUndefined/isNull check next time.
  1777 				var unresolvedSubModels = _.omit( this.prototype.subModelTypes, resolvedSubModels );
  1602 				if ( this._superModel && this._superModel.prototype.relations ) {
  1778 				_.each( unresolvedSubModels, function( subModelTypeName ) {
  1603 					// Find relations that exist on the `_superModel`, but not yet on this model.
  1779 					var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
  1604 					var inheritedRelations = _.select( this._superModel.prototype.relations || [], function( superRel ) {
  1780 					subModelType && subModelType.initializeModelHierarchy();
       
  1781 				});
       
  1782 			}
       
  1783 		},
       
  1784 
       
  1785 		inheritRelations: function() {
       
  1786 			// Bail out if we've been here before.
       
  1787 			if ( !_.isUndefined( this._superModel ) && !_.isNull( this._superModel ) ) {
       
  1788 				return;
       
  1789 			}
       
  1790 			// Try to initialize the _superModel.
       
  1791 			Backbone.Relational.store.setupSuperModel( this );
       
  1792 
       
  1793 			// If a superModel has been found, copy relations from the _superModel if they haven't been inherited automatically
       
  1794 			// (due to a redefinition of 'relations').
       
  1795 			if ( this._superModel ) {
       
  1796 				// The _superModel needs a chance to initialize its own inherited relations before we attempt to inherit relations
       
  1797 				// from the _superModel. You don't want to call 'initializeModelHierarchy' because that could cause sub-models of
       
  1798 				// this class to inherit their relations before this class has had chance to inherit it's relations.
       
  1799 				this._superModel.inheritRelations();
       
  1800 				if ( this._superModel.prototype.relations ) {
       
  1801 					// Find relations that exist on the '_superModel', but not yet on this model.
       
  1802 					var inheritedRelations = _.filter( this._superModel.prototype.relations || [], function( superRel ) {
  1605 						return !_.any( this.prototype.relations || [], function( rel ) {
  1803 						return !_.any( this.prototype.relations || [], function( rel ) {
  1606 							return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
  1804 							return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
  1607 						}, this );
  1805 						}, this );
  1608 					}, this );
  1806 					}, this );
  1609 
  1807 
  1610 					this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
  1808 					this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
  1611 				}
  1809 				}
  1612 				else {
  1810 			}
  1613 					this._superModel = false;
  1811 			// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail the
  1614 				}
  1812 			// isUndefined/isNull check next time.
  1615 			}
  1813 			else {
  1616 
  1814 				this._superModel = false;
  1617 			// If we came here through 'build' for a model that has 'subModelTypes', and not all of them have been resolved yet, try to resolve each.
       
  1618 			if ( this.prototype.subModelTypes && _.keys( this.prototype.subModelTypes ).length !== _.keys( this._subModels ).length ) {
       
  1619 				_.each( this.prototype.subModelTypes || [], function( subModelTypeName ) {
       
  1620 					var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
       
  1621 					subModelType && subModelType.initializeModelHierarchy();
       
  1622 				});
       
  1623 			}
  1815 			}
  1624 		},
  1816 		},
  1625 
  1817 
  1626 		/**
  1818 		/**
  1627 		 * Find an instance of `this` type in 'Backbone.Relational.store'.
  1819 		 * Find an instance of `this` type in 'Backbone.Relational.store'.
  1628 		 * - If `attributes` is a string or a number, `findOrCreate` will just query the `store` and return a model if found.
  1820 		 * A new model is created if no matching model is found, `attributes` is an object, and `options.create` is true.
  1629 		 * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
  1821 		 * - If `attributes` is a string or a number, `findOrCreate` will query the `store` and return a model if found.
  1630 		 *   Otherwise, a new model is created with `attributes` (unless `options.create` is explicitly set to `false`).
  1822 		 * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.merge` is `false`.
  1631 		 * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
  1823 		 * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
  1632 		 * @param {Object} [options]
  1824 		 * @param {Object} [options]
  1633 		 * @param {Boolean} [options.create=true]
  1825 		 * @param {Boolean} [options.create=true]
  1634 		 * @param {Boolean} [options.merge=true]
  1826 		 * @param {Boolean} [options.merge=true]
  1635 		 * @param {Boolean} [options.parse=false]
  1827 		 * @param {Boolean} [options.parse=false]
  1636 		 * @return {Backbone.RelationalModel}
  1828 		 * @return {Backbone.RelationalModel}
  1637 		 */
  1829 		 */
  1638 		findOrCreate: function( attributes, options ) {
  1830 		findOrCreate: function( attributes, options ) {
  1639 			options || ( options = {} );
  1831 			options || ( options = {} );
  1640 			var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
  1832 			var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
  1641 				this.prototype.parse( attributes ) : attributes;
  1833 				this.prototype.parse( _.clone( attributes ), options ) : attributes;
  1642 
  1834 
  1643 			// Try to find an instance of 'this' model type in the store
  1835 			// If specified, use a custom `find` function to match up existing models to the given attributes.
  1644 			var model = Backbone.Relational.store.find( this, parsedAttributes );
  1836 			// Otherwise, try to find an instance of 'this' model type in the store
       
  1837 			var model = this.findModel( parsedAttributes );
  1645 
  1838 
  1646 			// If we found an instance, update it with the data in 'item' (unless 'options.merge' is false).
  1839 			// If we found an instance, update it with the data in 'item' (unless 'options.merge' is false).
  1647 			// If not, create an instance (unless 'options.create' is false).
  1840 			// If not, create an instance (unless 'options.create' is false).
  1648 			if ( _.isObject( attributes ) ) {
  1841 			if ( _.isObject( attributes ) ) {
  1649 				if ( model && options.merge !== false ) {
  1842 				if ( model && options.merge !== false ) {
  1650 					// Make sure `options.collection` doesn't cascade to nested models
  1843 					// Make sure `options.collection` and `options.url` doesn't cascade to nested models
  1651 					delete options.collection;
  1844 					delete options.collection;
       
  1845 					delete options.url;
  1652 
  1846 
  1653 					model.set( parsedAttributes, options );
  1847 					model.set( parsedAttributes, options );
  1654 				}
  1848 				}
  1655 				else if ( !model && options.create !== false ) {
  1849 				else if ( !model && options.create !== false ) {
  1656 					model = this.build( attributes, options );
  1850 					model = this.build( parsedAttributes, _.defaults( { parse: false }, options ) );
  1657 				}
  1851 				}
  1658 			}
  1852 			}
  1659 
  1853 
  1660 			return model;
  1854 			return model;
       
  1855 		},
       
  1856 
       
  1857 		/**
       
  1858 		 * Find an instance of `this` type in 'Backbone.Relational.store'.
       
  1859 		 * - If `attributes` is a string or a number, `find` will query the `store` and return a model if found.
       
  1860 		 * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.merge` is `false`.
       
  1861 		 * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
       
  1862 		 * @param {Object} [options]
       
  1863 		 * @param {Boolean} [options.merge=true]
       
  1864 		 * @param {Boolean} [options.parse=false]
       
  1865 		 * @return {Backbone.RelationalModel}
       
  1866 		 */
       
  1867 		find: function( attributes, options ) {
       
  1868 			options || ( options = {} );
       
  1869 			options.create = false;
       
  1870 			return this.findOrCreate( attributes, options );
       
  1871 		},
       
  1872 
       
  1873 		/**
       
  1874 		 * A hook to override the matching when updating (or creating) a model.
       
  1875 		 * The default implementation is to look up the model by id in the store.
       
  1876 		 * @param {Object} attributes
       
  1877 		 * @returns {Backbone.RelationalModel}
       
  1878 		 */
       
  1879 		findModel: function( attributes ) {
       
  1880 			return Backbone.Relational.store.find( this, attributes );
  1661 		}
  1881 		}
  1662 	});
  1882 	});
  1663 	_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
  1883 	_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
  1664 
  1884 
  1665 	/**
  1885 	/**
  1667 	 * if the collection.model has subModels.
  1887 	 * if the collection.model has subModels.
  1668 	 * Attempts to find a model for `attrs` in Backbone.store through `findOrCreate`
  1888 	 * Attempts to find a model for `attrs` in Backbone.store through `findOrCreate`
  1669 	 * (which sets the new properties on it if found), or instantiates a new model.
  1889 	 * (which sets the new properties on it if found), or instantiates a new model.
  1670 	 */
  1890 	 */
  1671 	Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel;
  1891 	Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel;
  1672 	Backbone.Collection.prototype._prepareModel = function ( attrs, options ) {
  1892 	Backbone.Collection.prototype._prepareModel = function( attrs, options ) {
  1673 		var model;
  1893 		var model;
  1674 		
  1894 
  1675 		if ( attrs instanceof Backbone.Model ) {
  1895 		if ( attrs instanceof Backbone.Model ) {
  1676 			if ( !attrs.collection ) {
  1896 			if ( !attrs.collection ) {
  1677 				attrs.collection = this;
  1897 				attrs.collection = this;
  1678 			}
  1898 			}
  1679 			model = attrs;
  1899 			model = attrs;
  1680 		}
  1900 		}
  1681 		else {
  1901 		else {
  1682 			options || ( options = {} );
  1902 			options = options ? _.clone( options ) : {};
  1683 			options.collection = this;
  1903 			options.collection = this;
  1684 			
  1904 
  1685 			if ( typeof this.model.findOrCreate !== 'undefined' ) {
  1905 			if ( typeof this.model.findOrCreate !== 'undefined' ) {
  1686 				model = this.model.findOrCreate( attrs, options );
  1906 				model = this.model.findOrCreate( attrs, options );
  1687 			}
  1907 			}
  1688 			else {
  1908 			else {
  1689 				model = new this.model( attrs, options );
  1909 				model = new this.model( attrs, options );
  1690 			}
  1910 			}
  1691 			
  1911 
  1692 			if ( model && model.isNew() && !model._validate( attrs, options ) ) {
  1912 			if ( model && model.validationError ) {
  1693 				this.trigger( 'invalid', this, attrs, options );
  1913 				this.trigger( 'invalid', this, attrs, options );
  1694 				model = false;
  1914 				model = false;
  1695 			}
  1915 			}
  1696 		}
  1916 		}
  1697 		
  1917 
  1698 		return model;
  1918 		return model;
  1699 	};
  1919 	};
  1700 
  1920 
  1701 
  1921 
  1702 	/**
  1922 	/**
  1705 	 */
  1925 	 */
  1706 	var set = Backbone.Collection.prototype.__set = Backbone.Collection.prototype.set;
  1926 	var set = Backbone.Collection.prototype.__set = Backbone.Collection.prototype.set;
  1707 	Backbone.Collection.prototype.set = function( models, options ) {
  1927 	Backbone.Collection.prototype.set = function( models, options ) {
  1708 		// Short-circuit if this Collection doesn't hold RelationalModels
  1928 		// Short-circuit if this Collection doesn't hold RelationalModels
  1709 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  1929 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  1710 			return set.apply( this, arguments );
  1930 			return set.call( this, models, options );
  1711 		}
  1931 		}
  1712 
  1932 
  1713 		if ( options && options.parse ) {
  1933 		if ( options && options.parse ) {
  1714 			models = this.parse( models, options );
  1934 			models = this.parse( models, options );
  1715 		}
  1935 		}
  1716 
  1936 
  1717 		if ( !_.isArray( models ) ) {
  1937 		var singular = !_.isArray( models ),
  1718 			models = models ? [ models ] : [];
  1938 			newModels = [],
  1719 		}
  1939 			toAdd = [],
  1720 
  1940 			model = null;
  1721 		var newModels = [],
  1941 
  1722 			toAdd = [];
  1942 		models = singular ? ( models ? [ models ] : [] ) : _.clone( models );
  1723 
  1943 
  1724 		//console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
  1944 		//console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
  1725 		_.each( models, function( model ) {
  1945 		for ( var i = 0; i < models.length; i++ ) {
       
  1946 			model = models[i];
  1726 			if ( !( model instanceof Backbone.Model ) ) {
  1947 			if ( !( model instanceof Backbone.Model ) ) {
  1727 				model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
  1948 				model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
  1728 			}
  1949 			}
  1729 
       
  1730 			if ( model ) {
  1950 			if ( model ) {
  1731 				toAdd.push( model );
  1951 				toAdd.push( model );
  1732 
       
  1733 				if ( !( this.get( model ) || this.get( model.cid ) ) ) {
  1952 				if ( !( this.get( model ) || this.get( model.cid ) ) ) {
  1734 					newModels.push( model );
  1953 					newModels.push( model );
  1735 				}
  1954 				}
  1736 				// If we arrive in `add` while performing a `set` (after a create, so the model gains an `id`),
  1955 				// If we arrive in `add` while performing a `set` (after a create, so the model gains an `id`),
  1737 				// we may get here before `_onModelEvent` has had the chance to update `_byId`.
  1956 				// we may get here before `_onModelEvent` has had the chance to update `_byId`.
  1738 				else if ( model.id != null ) {
  1957 				else if ( model.id !== null && model.id !== undefined ) {
  1739 					this._byId[ model.id ] = model;
  1958 					this._byId[ model.id ] = model;
  1740 				}
  1959 				}
  1741 			}
  1960 			}
  1742 		}, this );
  1961 		}
  1743 
  1962 
  1744 		// Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
  1963 		// Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
  1745 		// If `parse` was specified, the collection and contained models have been parsed now.
  1964 		// If `parse` was specified, the collection and contained models have been parsed now.
  1746 		set.call( this, toAdd, _.defaults( { parse: false }, options ) );
  1965 		toAdd = singular ? ( toAdd.length ? toAdd[ 0 ] : null ) : toAdd;
  1747 
  1966 		var result = set.call( this, toAdd, _.defaults( { merge: false, parse: false }, options ) );
  1748 		_.each( newModels, function( model ) {
  1967 
       
  1968 		for ( i = 0; i < newModels.length; i++ ) {
       
  1969 			model = newModels[i];
  1749 			// Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
  1970 			// Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection.
  1750 			if ( this.get( model ) || this.get( model.cid ) ) {
  1971 			if ( this.get( model ) || this.get( model.cid ) ) {
  1751 				this.trigger( 'relational:add', model, this, options );
  1972 				this.trigger( 'relational:add', model, this, options );
  1752 			}
  1973 			}
  1753 		}, this );
  1974 		}
  1754 		
  1975 
  1755 		return this;
  1976 		return result;
  1756 	};
  1977 	};
  1757 
  1978 
  1758 	/**
  1979 	/**
  1759 	 * Override 'Backbone.Collection.remove' to trigger 'relational:remove'.
  1980 	 * Override 'Backbone.Collection._removeModels' to trigger 'relational:remove'.
  1760 	 */
  1981 	 */
  1761 	var remove = Backbone.Collection.prototype.__remove = Backbone.Collection.prototype.remove;
  1982 	var _removeModels = Backbone.Collection.prototype.___removeModels = Backbone.Collection.prototype._removeModels;
  1762 	Backbone.Collection.prototype.remove = function( models, options ) {
  1983 	Backbone.Collection.prototype._removeModels = function( models, options ) {
  1763 		// Short-circuit if this Collection doesn't hold RelationalModels
  1984 		// Short-circuit if this Collection doesn't hold RelationalModels
  1764 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  1985 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  1765 			return remove.apply( this, arguments );
  1986 			return _removeModels.call( this, models, options );
  1766 		}
  1987 		}
  1767 
       
  1768 		models = _.isArray( models ) ? models.slice() : [ models ];
       
  1769 		options || ( options = {} );
       
  1770 
  1988 
  1771 		var toRemove = [];
  1989 		var toRemove = [];
  1772 
  1990 
  1773 		//console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
  1991 		//console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
  1774 		_.each( models, function( model ) {
  1992 		_.each( models, function( model ) {
  1775 			model = this.get( model ) || this.get( model.cid );
  1993 			model = this.get( model ) || ( model && this.get( model.cid ) );
  1776 			model && toRemove.push( model );
  1994 			model && toRemove.push( model );
  1777 		}, this );
  1995 		}, this );
  1778 
  1996 
  1779 		if ( toRemove.length ) {
  1997 		var result = _removeModels.call( this, toRemove, options );
  1780 			remove.call( this, toRemove, options );
  1998 
  1781 
  1999 		_.each( toRemove, function( model ) {
  1782 			_.each( toRemove, function( model ) {
  2000 			this.trigger( 'relational:remove', model, this, options );
  1783 				this.trigger('relational:remove', model, this, options);
  2001 		}, this );
  1784 			}, this );
  2002 
  1785 		}
  2003 		return result;
  1786 		
       
  1787 		return this;
       
  1788 	};
  2004 	};
  1789 
  2005 
  1790 	/**
  2006 	/**
  1791 	 * Override 'Backbone.Collection.reset' to trigger 'relational:reset'.
  2007 	 * Override 'Backbone.Collection.reset' to trigger 'relational:reset'.
  1792 	 */
  2008 	 */
  1793 	var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
  2009 	var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
  1794 	Backbone.Collection.prototype.reset = function( models, options ) {
  2010 	Backbone.Collection.prototype.reset = function( models, options ) {
  1795 		options = _.extend( { merge: true }, options );
  2011 		options = _.extend( { merge: true }, options );
  1796 		reset.call( this, models, options );
  2012 		var result = reset.call( this, models, options );
  1797 
  2013 
  1798 		if ( this.model.prototype instanceof Backbone.RelationalModel ) {
  2014 		if ( this.model.prototype instanceof Backbone.RelationalModel ) {
  1799 			this.trigger( 'relational:reset', this, options );
  2015 			this.trigger( 'relational:reset', this, options );
  1800 		}
  2016 		}
  1801 
  2017 
  1802 		return this;
  2018 		return result;
  1803 	};
  2019 	};
  1804 
  2020 
  1805 	/**
  2021 	/**
  1806 	 * Override 'Backbone.Collection.sort' to trigger 'relational:reset'.
  2022 	 * Override 'Backbone.Collection.sort' to trigger 'relational:reset'.
  1807 	 */
  2023 	 */
  1808 	var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort;
  2024 	var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort;
  1809 	Backbone.Collection.prototype.sort = function( options ) {
  2025 	Backbone.Collection.prototype.sort = function( options ) {
  1810 		sort.call( this, options );
  2026 		var result = sort.call( this, options );
  1811 
  2027 
  1812 		if ( this.model.prototype instanceof Backbone.RelationalModel ) {
  2028 		if ( this.model.prototype instanceof Backbone.RelationalModel ) {
  1813 			this.trigger( 'relational:reset', this, options );
  2029 			this.trigger( 'relational:reset', this, options );
  1814 		}
  2030 		}
  1815 
  2031 
  1816 		return this;
  2032 		return result;
  1817 	};
  2033 	};
  1818 
  2034 
  1819 	/**
  2035 	/**
  1820 	 * Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations
  2036 	 * Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations
  1821 	 * are ready.
  2037 	 * are ready.
  1825 		// Short-circuit if this Collection doesn't hold RelationalModels
  2041 		// Short-circuit if this Collection doesn't hold RelationalModels
  1826 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  2042 		if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) {
  1827 			return trigger.apply( this, arguments );
  2043 			return trigger.apply( this, arguments );
  1828 		}
  2044 		}
  1829 
  2045 
  1830 		if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' ) {
  2046 		if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' || eventName === 'sort' ) {
  1831 			var dit = this,
  2047 			var dit = this,
  1832 				args = arguments;
  2048 				args = arguments;
  1833 			
  2049 
  1834 			if ( _.isObject( args[ 3 ] ) ) {
  2050 			if ( _.isObject( args[ 3 ] ) ) {
  1835 				args = _.toArray( args );
  2051 				args = _.toArray( args );
  1836 				// the fourth argument is the option object.
  2052 				// the fourth argument is the option object.
  1837 				// we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked
  2053 				// we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked
  1838 				args[ 3 ] = _.clone( args[ 3 ] );
  2054 				args[ 3 ] = _.clone( args[ 3 ] );
  1839 			}
  2055 			}
  1840 			
  2056 
  1841 			Backbone.Relational.eventQueue.add( function() {
  2057 			Backbone.Relational.eventQueue.add( function() {
  1842 				trigger.apply( dit, args );
  2058 				trigger.apply( dit, args );
  1843 			});
  2059 			});
  1844 		}
  2060 		}
  1845 		else {
  2061 		else {
  1846 			trigger.apply( this, arguments );
  2062 			trigger.apply( this, arguments );
  1847 		}
  2063 		}
  1848 		
  2064 
  1849 		return this;
  2065 		return this;
  1850 	};
  2066 	};
  1851 
  2067 
  1852 	// Override .extend() to automatically call .setup()
  2068 	// Override .extend() to automatically call .setup()
  1853 	Backbone.RelationalModel.extend = function( protoProps, classProps ) {
  2069 	Backbone.RelationalModel.extend = function( protoProps, classProps ) {
  1854 		var child = Backbone.Model.extend.apply( this, arguments );
  2070 		var child = Backbone.Model.extend.call( this, protoProps, classProps );
  1855 		
  2071 
  1856 		child.setup( this );
  2072 		child.setup( this );
  1857 
  2073 
  1858 		return child;
  2074 		return child;
  1859 	};
  2075 	};
  1860 })();
  2076 }));