arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/schema/documentarray.js
node_modules/mongoose/lib/schema/documentarray.jsRaw
  1/* eslint no-empty: 1 */
  2
  3/*!
  4 * Module dependencies.
  5 */
  6
  7var ArrayType = require('./array');
  8var CastError = require('../error/cast');
  9var MongooseDocumentArray = require('../types/documentarray');
 10var SchemaType = require('../schematype');
 11var Subdocument = require('../types/embedded');
 12var util = require('util');
 13
 14/**
 15 * SubdocsArray SchemaType constructor
 16 *
 17 * @param {String} key
 18 * @param {Schema} schema
 19 * @param {Object} options
 20 * @inherits SchemaArray
 21 * @api public
 22 */
 23
 24function DocumentArray(key, schema, options) {
 25  // compile an embedded document for this schema
 26  function EmbeddedDocument() {
 27    Subdocument.apply(this, arguments);
 28  }
 29
 30  EmbeddedDocument.prototype = Object.create(Subdocument.prototype);
 31  EmbeddedDocument.prototype.$__setSchema(schema);
 32  EmbeddedDocument.schema = schema;
 33
 34  // apply methods
 35  for (var i in schema.methods) {
 36    EmbeddedDocument.prototype[i] = schema.methods[i];
 37  }
 38
 39  // apply statics
 40  for (i in schema.statics) {
 41    EmbeddedDocument[i] = schema.statics[i];
 42  }
 43
 44  EmbeddedDocument.options = options;
 45
 46  ArrayType.call(this, key, EmbeddedDocument, options);
 47
 48  this.schema = schema;
 49  this.$isMongooseDocumentArray = true;
 50  var path = this.path;
 51  var fn = this.defaultValue;
 52
 53  if (!('defaultValue' in this) || fn !== void 0) {
 54    this.default(function() {
 55      var arr = fn.call(this);
 56      if (!Array.isArray(arr)) {
 57        arr = [arr];
 58      }
 59      return new MongooseDocumentArray(arr, path, this);
 60    });
 61  }
 62}
 63
 64/**
 65 * This schema type's name, to defend against minifiers that mangle
 66 * function names.
 67 *
 68 * @api public
 69 */
 70DocumentArray.schemaName = 'DocumentArray';
 71
 72/*!
 73 * Inherits from ArrayType.
 74 */
 75DocumentArray.prototype = Object.create(ArrayType.prototype);
 76DocumentArray.prototype.constructor = DocumentArray;
 77
 78/**
 79 * Performs local validations first, then validations on each embedded doc
 80 *
 81 * @api private
 82 */
 83
 84DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
 85  var _this = this;
 86  SchemaType.prototype.doValidate.call(this, array, function(err) {
 87    if (err) {
 88      return fn(err);
 89    }
 90
 91    var count = array && array.length;
 92    var error;
 93
 94    if (!count) {
 95      return fn();
 96    }
 97    if (options && options.updateValidator) {
 98      return fn();
 99    }
100
101    // handle sparse arrays, do not use array.forEach which does not
102    // iterate over sparse elements yet reports array.length including
103    // them :(
104
105    function callback(err) {
106      if (err) {
107        error = err;
108      }
109      --count || fn(error);
110    }
111
112    for (var i = 0, len = count; i < len; ++i) {
113      // sidestep sparse entries
114      var doc = array[i];
115      if (!doc) {
116        --count || fn(error);
117        continue;
118      }
119
120      // If you set the array index directly, the doc might not yet be
121      // a full fledged mongoose subdoc, so make it into one.
122      if (!(doc instanceof Subdocument)) {
123        doc = array[i] = new _this.casterConstructor(doc, array, undefined,
124            undefined, i);
125      }
126
127      // HACK: use $__original_validate to avoid promises so bluebird doesn't
128      // complain
129      if (doc.$__original_validate) {
130        doc.$__original_validate({__noPromise: true}, callback);
131      } else {
132        doc.validate({__noPromise: true}, callback);
133      }
134    }
135  }, scope);
136};
137
138/**
139 * Performs local validations first, then validations on each embedded doc.
140 *
141 * ####Note:
142 *
143 * This method ignores the asynchronous validators.
144 *
145 * @return {MongooseError|undefined}
146 * @api private
147 */
148
149DocumentArray.prototype.doValidateSync = function(array, scope) {
150  var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
151  if (schemaTypeError) {
152    return schemaTypeError;
153  }
154
155  var count = array && array.length,
156      resultError = null;
157
158  if (!count) {
159    return;
160  }
161
162  // handle sparse arrays, do not use array.forEach which does not
163  // iterate over sparse elements yet reports array.length including
164  // them :(
165
166  for (var i = 0, len = count; i < len; ++i) {
167    // only first error
168    if (resultError) {
169      break;
170    }
171    // sidestep sparse entries
172    var doc = array[i];
173    if (!doc) {
174      continue;
175    }
176
177    var subdocValidateError = doc.validateSync();
178
179    if (subdocValidateError) {
180      resultError = subdocValidateError;
181    }
182  }
183
184  return resultError;
185};
186
187/**
188 * Casts contents
189 *
190 * @param {Object} value
191 * @param {Document} document that triggers the casting
192 * @api private
193 */
194
195DocumentArray.prototype.cast = function(value, doc, init, prev, options) {
196  var selected,
197      subdoc,
198      i;
199
200  if (!Array.isArray(value)) {
201    // gh-2442 mark whole array as modified if we're initializing a doc from
202    // the db and the path isn't an array in the document
203    if (!!doc && init) {
204      doc.markModified(this.path);
205    }
206    return this.cast([value], doc, init, prev);
207  }
208
209  if (!(value && value.isMongooseDocumentArray) &&
210      (!options || !options.skipDocumentArrayCast)) {
211    value = new MongooseDocumentArray(value, this.path, doc);
212    if (prev && prev._handlers) {
213      for (var key in prev._handlers) {
214        doc.removeListener(key, prev._handlers[key]);
215      }
216    }
217  }
218
219  i = value.length;
220
221  while (i--) {
222    if (!value[i]) {
223      continue;
224    }
225    // Check if the document has a different schema (re gh-3701)
226    if ((value[i] instanceof Subdocument) &&
227        value[i].schema !== this.casterConstructor.schema) {
228      value[i] = value[i].toObject({virtuals: false});
229    }
230    if (!(value[i] instanceof Subdocument) && value[i]) {
231      if (init) {
232        selected || (selected = scopePaths(this, doc.$__.selected, init));
233        subdoc = new this.casterConstructor(null, value, true, selected, i);
234        value[i] = subdoc.init(value[i]);
235      } else {
236        try {
237          subdoc = prev.id(value[i]._id);
238        } catch (e) {
239        }
240
241        if (prev && subdoc) {
242          // handle resetting doc with existing id but differing data
243          // doc.array = [{ doc: 'val' }]
244          subdoc.set(value[i]);
245          // if set() is hooked it will have no return value
246          // see gh-746
247          value[i] = subdoc;
248        } else {
249          try {
250            subdoc = new this.casterConstructor(value[i], value, undefined,
251                undefined, i);
252            // if set() is hooked it will have no return value
253            // see gh-746
254            value[i] = subdoc;
255          } catch (error) {
256            var valueInErrorMessage = util.inspect(value[i]);
257            throw new CastError('embedded', valueInErrorMessage,
258              value._path, error);
259          }
260        }
261      }
262    }
263  }
264
265  return value;
266};
267
268/*!
269 * Scopes paths selected in a query to this array.
270 * Necessary for proper default application of subdocument values.
271 *
272 * @param {DocumentArray} array - the array to scope `fields` paths
273 * @param {Object|undefined} fields - the root fields selected in the query
274 * @param {Boolean|undefined} init - if we are being created part of a query result
275 */
276
277function scopePaths(array, fields, init) {
278  if (!(init && fields)) {
279    return undefined;
280  }
281
282  var path = array.path + '.',
283      keys = Object.keys(fields),
284      i = keys.length,
285      selected = {},
286      hasKeys,
287      key;
288
289  while (i--) {
290    key = keys[i];
291    if (key.indexOf(path) === 0) {
292      hasKeys || (hasKeys = true);
293      selected[key.substring(path.length)] = fields[key];
294    }
295  }
296
297  return hasKeys && selected || undefined;
298}
299
300/*!
301 * Module exports.
302 */
303
304module.exports = DocumentArray;