Files
biomedjs/node_modules/mongoose/lib/schema/documentarray.js

255 lines
6.1 KiB
JavaScript
Raw Permalink Normal View History

2015-11-24 22:08:58 -08:00
/* eslint no-empty: 1 */
2014-09-14 07:04:16 -04:00
/*!
* Module dependencies.
*/
2015-11-24 22:08:58 -08:00
var ArrayType = require('./array');
var CastError = require('../error/cast');
var MongooseDocumentArray = require('../types/documentarray');
var SchemaType = require('../schematype');
var Subdocument = require('../types/embedded');
2014-09-14 07:04:16 -04:00
/**
* SubdocsArray SchemaType constructor
*
* @param {String} key
* @param {Schema} schema
* @param {Object} options
* @inherits SchemaArray
* @api private
*/
2015-11-24 22:08:58 -08:00
function DocumentArray(key, schema, options) {
2014-09-14 07:04:16 -04:00
// compile an embedded document for this schema
2015-11-24 22:08:58 -08:00
function EmbeddedDocument() {
2014-09-14 07:04:16 -04:00
Subdocument.apply(this, arguments);
}
2015-11-24 22:08:58 -08:00
EmbeddedDocument.prototype = Object.create(Subdocument.prototype);
2014-09-14 07:04:16 -04:00
EmbeddedDocument.prototype.$__setSchema(schema);
EmbeddedDocument.schema = schema;
// apply methods
2015-11-24 22:08:58 -08:00
for (var i in schema.methods)
2014-09-14 07:04:16 -04:00
EmbeddedDocument.prototype[i] = schema.methods[i];
// apply statics
2015-11-24 22:08:58 -08:00
for (i in schema.statics)
2014-09-14 07:04:16 -04:00
EmbeddedDocument[i] = schema.statics[i];
EmbeddedDocument.options = options;
this.schema = schema;
ArrayType.call(this, key, EmbeddedDocument, options);
this.schema = schema;
var path = this.path;
var fn = this.defaultValue;
2015-11-24 22:08:58 -08:00
this.default(function() {
2014-09-14 07:04:16 -04:00
var arr = fn.call(this);
if (!Array.isArray(arr)) arr = [arr];
return new MongooseDocumentArray(arr, path, this);
});
2015-11-24 22:08:58 -08:00
}
/**
* This schema type's name, to defend against minifiers that mangle
* function names.
*
* @api private
*/
DocumentArray.schemaName = 'DocumentArray';
2014-09-14 07:04:16 -04:00
/*!
* Inherits from ArrayType.
*/
2015-11-24 22:08:58 -08:00
DocumentArray.prototype = Object.create( ArrayType.prototype );
DocumentArray.prototype.constructor = DocumentArray;
2014-09-14 07:04:16 -04:00
/**
* Performs local validations first, then validations on each embedded doc
*
* @api private
*/
2015-11-24 22:08:58 -08:00
DocumentArray.prototype.doValidate = function(array, fn, scope) {
SchemaType.prototype.doValidate.call(this, array, function(err) {
if (err) {
return fn(err);
}
2014-09-14 07:04:16 -04:00
2015-11-24 22:08:58 -08:00
var count = array && array.length;
var error;
2014-09-14 07:04:16 -04:00
if (!count) return fn();
// handle sparse arrays, do not use array.forEach which does not
// iterate over sparse elements yet reports array.length including
// them :(
for (var i = 0, len = count; i < len; ++i) {
// sidestep sparse entries
var doc = array[i];
if (!doc) {
2015-11-24 22:08:58 -08:00
--count || fn(error);
2014-09-14 07:04:16 -04:00
continue;
}
2015-11-24 22:08:58 -08:00
doc.validate(function(err) {
if (err) {
error = err;
}
--count || fn(error);
});
2014-09-14 07:04:16 -04:00
}
}, scope);
};
2015-11-24 22:08:58 -08:00
/**
* Performs local validations first, then validations on each embedded doc.
*
* ####Note:
*
* This method ignores the asynchronous validators.
*
* @return {MongooseError|undefined}
* @api private
*/
DocumentArray.prototype.doValidateSync = function(array, scope) {
var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
if (schemaTypeError) return schemaTypeError;
var count = array && array.length,
resultError = null;
if (!count) return;
// handle sparse arrays, do not use array.forEach which does not
// iterate over sparse elements yet reports array.length including
// them :(
for (var i = 0, len = count; i < len; ++i) {
// only first error
if ( resultError ) break;
// sidestep sparse entries
var doc = array[i];
if (!doc) continue;
var subdocValidateError = doc.validateSync();
if (subdocValidateError) {
resultError = subdocValidateError;
}
}
return resultError;
};
2014-09-14 07:04:16 -04:00
/**
* Casts contents
*
* @param {Object} value
* @param {Document} document that triggers the casting
* @api private
*/
2015-11-24 22:08:58 -08:00
DocumentArray.prototype.cast = function(value, doc, init, prev) {
var selected,
subdoc,
i;
2014-09-14 07:04:16 -04:00
if (!Array.isArray(value)) {
2015-11-24 22:08:58 -08:00
// gh-2442 mark whole array as modified if we're initializing a doc from
// the db and the path isn't an array in the document
if (!!doc && init) {
doc.markModified(this.path);
}
2014-09-14 07:04:16 -04:00
return this.cast([value], doc, init, prev);
}
2015-11-24 22:08:58 -08:00
if (!(value && value.isMongooseDocumentArray)) {
2014-09-14 07:04:16 -04:00
value = new MongooseDocumentArray(value, this.path, doc);
2015-11-24 22:08:58 -08:00
if (prev && prev._handlers) {
for (var key in prev._handlers) {
doc.removeListener(key, prev._handlers[key]);
}
}
2014-09-14 07:04:16 -04:00
}
i = value.length;
while (i--) {
if (!(value[i] instanceof Subdocument) && value[i]) {
if (init) {
selected || (selected = scopePaths(this, doc.$__.selected, init));
2015-11-24 22:08:58 -08:00
subdoc = new this.casterConstructor(null, value, true, selected, i);
2014-09-14 07:04:16 -04:00
value[i] = subdoc.init(value[i]);
} else {
2015-11-24 22:08:58 -08:00
try {
subdoc = prev.id(value[i]._id);
} catch (e) {}
if (prev && subdoc) {
2014-09-14 07:04:16 -04:00
// handle resetting doc with existing id but differing data
// doc.array = [{ doc: 'val' }]
subdoc.set(value[i]);
2015-11-24 22:08:58 -08:00
// if set() is hooked it will have no return value
// see gh-746
value[i] = subdoc;
2014-09-14 07:04:16 -04:00
} else {
2015-11-24 22:08:58 -08:00
try {
subdoc = new this.casterConstructor(value[i], value, undefined,
undefined, i);
// if set() is hooked it will have no return value
// see gh-746
value[i] = subdoc;
} catch (error) {
throw new CastError('embedded', value[i], value._path);
}
2014-09-14 07:04:16 -04:00
}
}
}
}
return value;
2015-11-24 22:08:58 -08:00
};
2014-09-14 07:04:16 -04:00
/*!
* Scopes paths selected in a query to this array.
* Necessary for proper default application of subdocument values.
*
* @param {DocumentArray} array - the array to scope `fields` paths
* @param {Object|undefined} fields - the root fields selected in the query
* @param {Boolean|undefined} init - if we are being created part of a query result
*/
2015-11-24 22:08:58 -08:00
function scopePaths(array, fields, init) {
2014-09-14 07:04:16 -04:00
if (!(init && fields)) return undefined;
2015-11-24 22:08:58 -08:00
var path = array.path + '.',
keys = Object.keys(fields),
i = keys.length,
selected = {},
hasKeys,
key;
2014-09-14 07:04:16 -04:00
while (i--) {
key = keys[i];
if (0 === key.indexOf(path)) {
hasKeys || (hasKeys = true);
selected[key.substring(path.length)] = fields[key];
}
}
return hasKeys && selected || undefined;
}
/*!
* Module exports.
*/
module.exports = DocumentArray;