You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
4.4 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var SchemaType = require('../schematype')
  5. , ArrayType = require('./array')
  6. , MongooseDocumentArray = require('../types/documentarray')
  7. , Subdocument = require('../types/embedded')
  8. , Document = require('../document');
  9. /**
  10. * SubdocsArray SchemaType constructor
  11. *
  12. * @param {String} key
  13. * @param {Schema} schema
  14. * @param {Object} options
  15. * @inherits SchemaArray
  16. * @api private
  17. */
  18. function DocumentArray (key, schema, options) {
  19. // compile an embedded document for this schema
  20. function EmbeddedDocument () {
  21. Subdocument.apply(this, arguments);
  22. }
  23. EmbeddedDocument.prototype.__proto__ = Subdocument.prototype;
  24. EmbeddedDocument.prototype.$__setSchema(schema);
  25. EmbeddedDocument.schema = schema;
  26. // apply methods
  27. for (var i in schema.methods) {
  28. EmbeddedDocument.prototype[i] = schema.methods[i];
  29. }
  30. // apply statics
  31. for (var i in schema.statics)
  32. EmbeddedDocument[i] = schema.statics[i];
  33. EmbeddedDocument.options = options;
  34. this.schema = schema;
  35. ArrayType.call(this, key, EmbeddedDocument, options);
  36. this.schema = schema;
  37. var path = this.path;
  38. var fn = this.defaultValue;
  39. this.default(function(){
  40. var arr = fn.call(this);
  41. if (!Array.isArray(arr)) arr = [arr];
  42. return new MongooseDocumentArray(arr, path, this);
  43. });
  44. };
  45. /*!
  46. * Inherits from ArrayType.
  47. */
  48. DocumentArray.prototype.__proto__ = ArrayType.prototype;
  49. /**
  50. * Performs local validations first, then validations on each embedded doc
  51. *
  52. * @api private
  53. */
  54. DocumentArray.prototype.doValidate = function (array, fn, scope) {
  55. var self = this;
  56. SchemaType.prototype.doValidate.call(this, array, function (err) {
  57. if (err) return fn(err);
  58. var count = array && array.length
  59. , error;
  60. if (!count) return fn();
  61. // handle sparse arrays, do not use array.forEach which does not
  62. // iterate over sparse elements yet reports array.length including
  63. // them :(
  64. for (var i = 0, len = count; i < len; ++i) {
  65. // sidestep sparse entries
  66. var doc = array[i];
  67. if (!doc) {
  68. --count || fn();
  69. continue;
  70. }
  71. ;(function (i) {
  72. doc.validate(function (err) {
  73. if (err && !error) {
  74. // rewrite the key
  75. err.key = self.key + '.' + i + '.' + err.key;
  76. return fn(error = err);
  77. }
  78. --count || fn();
  79. });
  80. })(i);
  81. }
  82. }, scope);
  83. };
  84. /**
  85. * Casts contents
  86. *
  87. * @param {Object} value
  88. * @param {Document} document that triggers the casting
  89. * @api private
  90. */
  91. DocumentArray.prototype.cast = function (value, doc, init, prev) {
  92. var selected
  93. , subdoc
  94. , i
  95. if (!Array.isArray(value)) {
  96. return this.cast([value], doc, init, prev);
  97. }
  98. if (!(value instanceof MongooseDocumentArray)) {
  99. value = new MongooseDocumentArray(value, this.path, doc);
  100. }
  101. i = value.length;
  102. while (i--) {
  103. if (!(value[i] instanceof Subdocument) && value[i]) {
  104. if (init) {
  105. selected || (selected = scopePaths(this, doc.$__.selected, init));
  106. subdoc = new this.casterConstructor(null, value, true, selected);
  107. value[i] = subdoc.init(value[i]);
  108. } else {
  109. if (prev && (subdoc = prev.id(value[i]._id))) {
  110. // handle resetting doc with existing id but differing data
  111. // doc.array = [{ doc: 'val' }]
  112. subdoc.set(value[i]);
  113. } else {
  114. subdoc = new this.casterConstructor(value[i], value);
  115. }
  116. // if set() is hooked it will have no return value
  117. // see gh-746
  118. value[i] = subdoc;
  119. }
  120. }
  121. }
  122. return value;
  123. }
  124. /*!
  125. * Scopes paths selected in a query to this array.
  126. * Necessary for proper default application of subdocument values.
  127. *
  128. * @param {DocumentArray} array - the array to scope `fields` paths
  129. * @param {Object|undefined} fields - the root fields selected in the query
  130. * @param {Boolean|undefined} init - if we are being created part of a query result
  131. */
  132. function scopePaths (array, fields, init) {
  133. if (!(init && fields)) return undefined;
  134. var path = array.path + '.'
  135. , keys = Object.keys(fields)
  136. , i = keys.length
  137. , selected = {}
  138. , hasKeys
  139. , key
  140. while (i--) {
  141. key = keys[i];
  142. if (0 === key.indexOf(path)) {
  143. hasKeys || (hasKeys = true);
  144. selected[key.substring(path.length)] = fields[key];
  145. }
  146. }
  147. return hasKeys && selected || undefined;
  148. }
  149. /*!
  150. * Module exports.
  151. */
  152. module.exports = DocumentArray;