index.js
function(model, options, finish) {
	var asyncCreator = async() // Task runner that actually creates all the Mongo records
		// Deal with timeout errors (usually unsolvable circular references) {{{
		.timeout(settings.timeout || 2000, function() {
			var taskIDs = {};
			var remaining = this._struct
				// Prepare a lookup table of tasks IDs that have already finished {{{
				.map(function(task) {
					if (task.completed) taskIDs[_(task.payload).keys().first()] = true;
					return task;
				})
				// }}}
				// Remove non defered objects + completed tasks {{{
				.filter(function(task) {
					return (task.type == 'deferObject' && !task.completed);
				})
				// }}}
				// Remove any task that has resolved prereqs {{{
				.filter(function(task) {
					if (!task.prereq.length) return true; // Has no prereqs anyway
					return ! task.prereq
						.every(function(prereq) {
							return (!! taskIDs[prereq]);
						});
				})
				// }}}
				// Remove any task that nothing else depends on {{{
				.filter(function(task) {
					return true;
				});
				// }}}

		finish(
			'Unresolvable circular reference\n' +
			'Remaining refs:\n' +
			remaining
				// Format the output {{{
				.map(function(task) {
					return (
						' * ' +
						(_(task.payload).keys().first() || '???') +
						(task.prereq.length > 0 ? ' (Requires: ' + task.prereq
							.filter(function(prereq) {
								return (! taskIDs[prereq]); // Pre-req resolved ok?
							})
							.join(', ')
						+ ')': '')
					);
				})
				.join('\n')
				// }}}
			, {
				unresolved: remaining.map(function(task) {
					return _(task.payload).keys().first();
				}),
				processed: this._struct
					.filter(function(task) {
						return (task.type == 'deferObject' && task.completed);
					})
					.length,
			});
		});
		// }}}

	async()
		.then(function(next) { // Coherce args into scenario(<model>, [options], [callback]) {{{
			if (!model) throw new Error('No arguments passed to scenario()');
			if (_.isFunction(options)) { // Form: (model, callback)
				finish = options;
			} else if (options) { // Form: model(model, options)
				_.merge(settings, options);
			}
			if (!finish) finish = function() {};

			next();
		}) // }}}
		.then(function(next) { // Sanity checks {{{
			if (!settings.connection) throw new Error('Invalid connection to Mongoose');
			if (!_.isObject(model)) throw new Error('Invalid scenario invoke style - scenario(' + typeof model + ')');
			next();
		}) // }}}
		.then(function(next) { // Reset all state variables {{{
			settings.progress.created = {};
			if (settings.reset) settings.refs = {};
			next();
		}) /// }}}
		.then(function(next) { // Optionally Nuke existing models {{{
			if (!settings.nuke) return next();
			async()
				.set('models', _.isArray(settings.nuke) ? settings.nuke : settings.getModels())
				.forEach('models', function(next, model) {
					var collection = settings.getCollection(model);
					if (!collection) return next('Model "' + model + '" is present in the Scenario schema but no model can be found matching that name, did you forget to load it?');
					collection.remove({}, function(err) {
						if (err) next(err);
						settings.progress.nuked.push(model);
						next();
					});
				})
				.end(next);
		}) // }}}
		.forEach(model, function(next, rows, collection) { // Compute FKs for each model {{{
			if (settings.knownFK[collection]) return next(); // Already computed the FKs for this collection

			settings.knownFK[collection] = {};

			var collectionSchema = settings.getCollectionSchema(collection);
			if (!collectionSchema) throw new Error('Collection "' + collection + '" not found in Mongoose schema. Did you forget to load its model?');

			// Merge extracted keys into knownFKs storage
			settings.knownFK[collection] = extractFKs(collectionSchema);

			next();
		}) // }}}
		.forEach(model, function(next, rows, collection) { // Process the Scenario profile {{{
			async()
				.forEach(rows, function(next, row) { // Split all incomming items into defered tasks {{{
					var rowFlattened = flatten(row);
					var id = row[settings.keys.ref] ? row[settings.keys.ref] : 'anon-' + settings.idVal++;
					var dependents = determineFKs(rowFlattened, settings.knownFK[collection]);
					var rowUnflattened = unflatten(rowFlattened);

					asyncCreator.defer(dependents, id, function(next) {
						createRow(collection, id, rowUnflattened, next);
					});

					next();
				}) // }}}
				.end(next);
		}) // }}}
		.then(function(next) { // Run all tasks {{{
			asyncCreator
				.await()
				.end(next);
		}) // }}}
		// End {{{
		.end(function(err) {
			if (err) return finish(err);
			finish(null, settings.progress);
		});
		// }}}

	return scenarioImport;
}
function(model, options, finish) {
	var asyncCreator = async() // Task runner that actually creates all the Mongo records
		// Deal with timeout errors (usually unsolvable circular references) {{{
		.timeout(settings.timeout || 2000, function() {
			var taskIDs = {};
			var remaining = this._struct
				// Prepare a lookup table of tasks IDs that have already finished {{{
				.map(function(task) {
					if (task.completed) taskIDs[_(task.payload).keys().first()] = true;
					return task;
				})
				// }}}
				// Remove non defered objects + completed tasks {{{
				.filter(function(task) {
					return (task.type == 'deferObject' && !task.completed);
				})
				// }}}
				// Remove any task that has resolved prereqs {{{
				.filter(function(task) {
					if (!task.prereq.length) return true; // Has no prereqs anyway
					return ! task.prereq
						.every(function(prereq) {
							return (!! taskIDs[prereq]);
						});
				})
				// }}}
				// Remove any task that nothing else depends on {{{
				.filter(function(task) {
					return true;
				});
				// }}}

		finish(
			'Unresolvable circular reference\n' +
			'Remaining refs:\n' +
			remaining
				// Format the output {{{
				.map(function(task) {
					return (
						' * ' +
						(_(task.payload).keys().first() || '???') +
						(task.prereq.length > 0 ? ' (Requires: ' + task.prereq
							.filter(function(prereq) {
								return (! taskIDs[prereq]); // Pre-req resolved ok?
							})
							.join(', ')
						+ ')': '')
					);
				})
				.join('\n')
				// }}}
			, {
				unresolved: remaining.map(function(task) {
					return _(task.payload).keys().first();
				}),
				processed: this._struct
					.filter(function(task) {
						return (task.type == 'deferObject' && task.completed);
					})
					.length,
			});
		});
		// }}}

	async()
		.then(function(next) { // Coherce args into scenario(<model>, [options], [callback]) {{{
			if (!model) throw new Error('No arguments passed to scenario()');
			if (_.isFunction(options)) { // Form: (model, callback)
				finish = options;
			} else if (options) { // Form: model(model, options)
				_.merge(settings, options);
			}
			if (!finish) finish = function() {};

			next();
		}) // }}}
		.then(function(next) { // Sanity checks {{{
			if (!settings.connection) throw new Error('Invalid connection to Mongoose');
			if (!_.isObject(model)) throw new Error('Invalid scenario invoke style - scenario(' + typeof model + ')');
			next();
		}) // }}}
		.then(function(next) { // Reset all state variables {{{
			settings.progress.created = {};
			if (settings.reset) settings.refs = {};
			next();
		}) /// }}}
		.then(function(next) { // Optionally Nuke existing models {{{
			if (!settings.nuke) return next();
			async()
				.set('models', _.isArray(settings.nuke) ? settings.nuke : settings.getModels())
				.forEach('models', function(next, model) {
					var collection = settings.getCollection(model);
					if (!collection) return next('Model "' + model + '" is present in the Scenario schema but no model can be found matching that name, did you forget to load it?');
					collection.remove({}, function(err) {
						if (err) next(err);
						settings.progress.nuked.push(model);
						next();
					});
				})
				.end(next);
		}) // }}}
		.forEach(model, function(next, rows, collection) { // Compute FKs for each model {{{
			if (settings.knownFK[collection]) return next(); // Already computed the FKs for this collection

			settings.knownFK[collection] = {};

			var collectionSchema = settings.getCollectionSchema(collection);
			if (!collectionSchema) throw new Error('Collection "' + collection + '" not found in Mongoose schema. Did you forget to load its model?');

			// Merge extracted keys into knownFKs storage
			settings.knownFK[collection] = extractFKs(collectionSchema);

			next();
		}) // }}}
		.forEach(model, function(next, rows, collection) { // Process the Scenario profile {{{
			async()
				.forEach(rows, function(next, row) { // Split all incomming items into defered tasks {{{
					var rowFlattened = flatten(row);
					var id = row[settings.keys.ref] ? row[settings.keys.ref] : 'anon-' + settings.idVal++;
					var dependents = determineFKs(rowFlattened, settings.knownFK[collection]);
					var rowUnflattened = unflatten(rowFlattened);

					asyncCreator.defer(dependents, id, function(next) {
						createRow(collection, id, rowUnflattened, next);
					});

					next();
				}) // }}}
				.end(next);
		}) // }}}
		.then(function(next) { // Run all tasks {{{
			asyncCreator
				.await()
				.end(next);
		}) // }}}
		// End {{{
		.end(function(err) {
			if (err) return finish(err);
			finish(null, settings.progress);
		});
		// }}}

	return scenarioImport;
}
function ( model , options , finish ) { var asyncCreator = async ( ) // Task runner that actually creates all the Mongo records // Deal with timeout errors (usually unsolvable circular references) {{{ . timeout ( settings . timeout || 2000 , function ( ) { var taskIDs = { } ; var remaining = this . _struct // Prepare a lookup table of tasks IDs that have already finished {{{ . map ( function ( task ) { if ( task . completed ) taskIDs [ _ ( task . payload ) . keys ( ) . first ( ) ] = true ; return task ; } ) // }}} // Remove non defered objects + completed tasks {{{ . filter ( function ( task ) { return ( task . type == 'deferObject' && ! task . completed ) ; } ) // }}} // Remove any task that has resolved prereqs {{{ . filter ( function ( task ) { if ( ! task . prereq . length ) return true ; // Has no prereqs anyway return ! task . prereq . every ( function ( prereq ) { return ( ! ! taskIDs [ prereq ] ) ; } ) ; } ) // }}} // Remove any task that nothing else depends on {{{ . filter ( function ( task ) { return true ; } ) ; // }}} finish ( 'Unresolvable circular reference\n' + 'Remaining refs:\n' + remaining // Format the output {{{ . map ( function ( task ) { return ( ' * ' + ( _ ( task . payload ) . keys ( ) . first ( ) || '???' ) + ( task . prereq . length > 0 ? ' (Requires: ' + task . prereq . filter ( function ( prereq ) { return ( ! taskIDs [ prereq ] ) ; // Pre-req resolved ok? } ) . join ( ', ' ) + ')' : '' ) ) ; } ) . join ( '\n' ) // }}} , { unresolved : remaining . map ( function ( task ) { return _ ( task . payload ) . keys ( ) . first ( ) ; } ) , processed : this . _struct . filter ( function ( task ) { return ( task . type == 'deferObject' && task . completed ) ; } ) . length , } ) ; } ) ; // }}} async ( ) . then ( function ( next ) { // Coherce args into scenario(<model>, [options], [callback]) {{{ if ( ! model ) throw new Error ( 'No arguments passed to scenario()' ) ; if ( _ . isFunction ( options ) ) { // Form: (model, callback) finish = options ; } else if ( options ) { // Form: model(model, options) _ . merge ( settings , options ) ; } if ( ! finish ) finish = function ( ) { } ; next ( ) ; } ) // }}} . then ( function ( next ) { // Sanity checks {{{ if ( ! settings . connection ) throw new Error ( 'Invalid connection to Mongoose' ) ; if ( ! _ . isObject ( model ) ) throw new Error ( 'Invalid scenario invoke style - scenario(' + typeof model + ')' ) ; next ( ) ; } ) // }}} . then ( function ( next ) { // Reset all state variables {{{ settings . progress . created = { } ; if ( settings . reset ) settings . refs = { } ; next ( ) ; } ) /// }}} . then ( function ( next ) { // Optionally Nuke existing models {{{ if ( ! settings . nuke ) return next ( ) ; async ( ) . set ( 'models' , _ . isArray ( settings . nuke ) ? settings . nuke : settings . getModels ( ) ) . forEach ( 'models' , function ( next , model ) { var collection = settings . getCollection ( model ) ; if ( ! collection ) return next ( 'Model "' + model + '" is present in the Scenario schema but no model can be found matching that name, did you forget to load it?' ) ; collection . remove ( { } , function ( err ) { if ( err ) next ( err ) ; settings . progress . nuked . push ( model ) ; next ( ) ; } ) ; } ) . end ( next ) ; } ) // }}} . forEach ( model , function ( next , rows , collection ) { // Compute FKs for each model {{{ if ( settings . knownFK [ collection ] ) return next ( ) ; // Already computed the FKs for this collection settings . knownFK [ collection ] = { } ; var collectionSchema = settings . getCollectionSchema ( collection ) ; if ( ! collectionSchema ) throw new Error ( 'Collection "' + collection + '" not found in Mongoose schema. Did you forget to load its model?' ) ; // Merge extracted keys into knownFKs storage settings . knownFK [ collection ] = extractFKs ( collectionSchema ) ; next ( ) ; } ) // }}} . forEach ( model , function ( next , rows , collection ) { // Process the Scenario profile {{{ async ( ) . forEach ( rows , function ( next , row ) { // Split all incomming items into defered tasks {{{ var rowFlattened = flatten ( row ) ; var id = row [ settings . keys . ref ] ? row [ settings . keys . ref ] : 'anon-' + settings . idVal ++ ; var dependents = determineFKs ( rowFlattened , settings . knownFK [ collection ] ) ; var rowUnflattened = unflatten ( rowFlattened ) ; asyncCreator . defer ( dependents , id , function ( next ) { createRow ( collection , id , rowUnflattened , next ) ; } ) ; next ( ) ; } ) // }}} . end ( next ) ; } ) // }}} . then ( function ( next ) { // Run all tasks {{{ asyncCreator . await ( ) . end ( next ) ; } ) // }}} // End {{{ . end ( function ( err ) { if ( err ) return finish ( err ) ; finish ( null , settings . progress ) ; } ) ; // }}} return scenarioImport ; }
Import a scenario file into a Mongo database
A scenario must be complete - i.e. have no dangling references for it to suceed
@param {Object} model The scenario to create - expected format is a hash of collection names each containing a collection of records (e.g. `{users: [{name: 'user1'}, {name: 'user2'}] }`)
@param {Object} settings Optional Settings array to import
@param {function} finish(err, data) Optional callback fired when scenario finishes creating records
Import a scenario file into a Mongo database A scenario must be complete - i . e . have no dangling references for it to suceed