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.

316 lines
7.4 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var SchemaType = require('../schematype')
  5. , CastError = SchemaType.CastError
  6. , NumberSchema = require('./number')
  7. , Types = {
  8. Boolean: require('./boolean')
  9. , Date: require('./date')
  10. , Number: require('./number')
  11. , String: require('./string')
  12. , ObjectId: require('./objectid')
  13. , Buffer: require('./buffer')
  14. }
  15. , MongooseArray = require('../types').Array
  16. , EmbeddedDoc = require('../types').Embedded
  17. , Mixed = require('./mixed')
  18. , Query = require('../query')
  19. , utils = require('../utils')
  20. , isMongooseObject = utils.isMongooseObject
  21. /**
  22. * Array SchemaType constructor
  23. *
  24. * @param {String} key
  25. * @param {SchemaType} cast
  26. * @param {Object} options
  27. * @inherits SchemaType
  28. * @api private
  29. */
  30. function SchemaArray (key, cast, options) {
  31. if (cast) {
  32. var castOptions = {};
  33. if ('Object' === cast.constructor.name) {
  34. if (cast.type) {
  35. // support { type: Woot }
  36. castOptions = utils.clone(cast); // do not alter user arguments
  37. delete castOptions.type;
  38. cast = cast.type;
  39. } else {
  40. cast = Mixed;
  41. }
  42. }
  43. // support { type: 'String' }
  44. var name = 'string' == typeof cast
  45. ? cast
  46. : cast.name;
  47. var caster = name in Types
  48. ? Types[name]
  49. : cast;
  50. this.casterConstructor = caster;
  51. this.caster = new caster(null, castOptions);
  52. if (!(this.caster instanceof EmbeddedDoc)) {
  53. this.caster.path = key;
  54. }
  55. }
  56. SchemaType.call(this, key, options);
  57. var self = this
  58. , defaultArr
  59. , fn;
  60. if (this.defaultValue) {
  61. defaultArr = this.defaultValue;
  62. fn = 'function' == typeof defaultArr;
  63. }
  64. this.default(function(){
  65. var arr = fn ? defaultArr() : defaultArr || [];
  66. return new MongooseArray(arr, self.path, this);
  67. });
  68. };
  69. /*!
  70. * Inherits from SchemaType.
  71. */
  72. SchemaArray.prototype.__proto__ = SchemaType.prototype;
  73. /**
  74. * Check required
  75. *
  76. * @param {Array} value
  77. * @api private
  78. */
  79. SchemaArray.prototype.checkRequired = function (value) {
  80. return !!(value && value.length);
  81. };
  82. /**
  83. * Overrides the getters application for the population special-case
  84. *
  85. * @param {Object} value
  86. * @param {Object} scope
  87. * @api private
  88. */
  89. SchemaArray.prototype.applyGetters = function (value, scope) {
  90. if (this.caster.options && this.caster.options.ref) {
  91. // means the object id was populated
  92. return value;
  93. }
  94. return SchemaType.prototype.applyGetters.call(this, value, scope);
  95. };
  96. /**
  97. * Casts contents
  98. *
  99. * @param {Object} value
  100. * @param {Document} doc document that triggers the casting
  101. * @param {Boolean} init whether this is an initialization cast
  102. * @api private
  103. */
  104. SchemaArray.prototype.cast = function (value, doc, init) {
  105. if (Array.isArray(value)) {
  106. if (!(value instanceof MongooseArray)) {
  107. value = new MongooseArray(value, this.path, doc);
  108. }
  109. if (this.caster) {
  110. try {
  111. for (var i = 0, l = value.length; i < l; i++) {
  112. value[i] = this.caster.cast(value[i], doc, init);
  113. }
  114. } catch (e) {
  115. // rethrow
  116. throw new CastError(e.type, value, this.path);
  117. }
  118. }
  119. return value;
  120. } else {
  121. return this.cast([value], doc, init);
  122. }
  123. };
  124. /**
  125. * Casts contents for queries.
  126. *
  127. * @param {String} $conditional
  128. * @param {any} [value]
  129. * @api private
  130. */
  131. SchemaArray.prototype.castForQuery = function ($conditional, value) {
  132. var handler
  133. , val;
  134. if (arguments.length === 2) {
  135. handler = this.$conditionalHandlers[$conditional];
  136. if (!handler)
  137. throw new Error("Can't use " + $conditional + " with Array.");
  138. val = handler.call(this, value);
  139. } else {
  140. val = $conditional;
  141. var proto = this.casterConstructor.prototype;
  142. var method = proto.castForQuery || proto.cast;
  143. var caster = this.caster;
  144. if (Array.isArray(val)) {
  145. val = val.map(function (v) {
  146. if (method) v = method.call(caster, v);
  147. return isMongooseObject(v)
  148. ? v.toObject()
  149. : v;
  150. });
  151. } else if (method) {
  152. val = method.call(caster, val);
  153. }
  154. }
  155. return val && isMongooseObject(val)
  156. ? val.toObject()
  157. : val;
  158. };
  159. /*!
  160. * @ignore
  161. */
  162. function castToNumber (val) {
  163. return Types.Number.prototype.cast.call(this, val);
  164. }
  165. function castArray (arr, self) {
  166. self || (self = this);
  167. arr.forEach(function (v, i) {
  168. if (Array.isArray(v)) {
  169. castArray(v, self);
  170. } else {
  171. arr[i] = castToNumber.call(self, v);
  172. }
  173. });
  174. }
  175. SchemaArray.prototype.$conditionalHandlers = {
  176. '$all': function handle$all (val) {
  177. if (!Array.isArray(val)) {
  178. val = [val];
  179. }
  180. val = val.map(function (v) {
  181. if (v && 'Object' === v.constructor.name) {
  182. var o = {};
  183. o[this.path] = v;
  184. var query = new Query(o);
  185. query.cast(this.casterConstructor);
  186. return query._conditions[this.path];
  187. }
  188. return v;
  189. }, this);
  190. return this.castForQuery(val);
  191. }
  192. , '$elemMatch': function (val) {
  193. if (val.$in) {
  194. val.$in = this.castForQuery('$in', val.$in);
  195. return val;
  196. }
  197. var query = new Query(val);
  198. query.cast(this.casterConstructor);
  199. return query._conditions;
  200. }
  201. , '$size': castToNumber
  202. , '$ne': SchemaArray.prototype.castForQuery
  203. , '$in': SchemaArray.prototype.castForQuery
  204. , '$nin': SchemaArray.prototype.castForQuery
  205. , '$regex': SchemaArray.prototype.castForQuery
  206. , '$options': String
  207. , '$near': SchemaArray.prototype.castForQuery
  208. , '$nearSphere': SchemaArray.prototype.castForQuery
  209. , '$gt': SchemaArray.prototype.castForQuery
  210. , '$gte': SchemaArray.prototype.castForQuery
  211. , '$lt': SchemaArray.prototype.castForQuery
  212. , '$lte': SchemaArray.prototype.castForQuery
  213. , '$within': function (val) {
  214. var self = this;
  215. if (val.$maxDistance) {
  216. val.$maxDistance = castToNumber.call(this, val.$maxDistance);
  217. }
  218. if (val.$box || val.$polygon) {
  219. var type = val.$box ? '$box' : '$polygon';
  220. val[type].forEach(function (arr) {
  221. if (!Array.isArray(arr)) {
  222. var msg = 'Invalid $within $box argument. '
  223. + 'Expected an array, received ' + arr;
  224. throw new TypeError(msg);
  225. }
  226. arr.forEach(function (v, i) {
  227. arr[i] = castToNumber.call(this, v);
  228. });
  229. })
  230. } else if (val.$center || val.$centerSphere) {
  231. var type = val.$center ? '$center' : '$centerSphere';
  232. val[type].forEach(function (item, i) {
  233. if (Array.isArray(item)) {
  234. item.forEach(function (v, j) {
  235. item[j] = castToNumber.call(this, v);
  236. });
  237. } else {
  238. val[type][i] = castToNumber.call(this, item);
  239. }
  240. })
  241. } else if (val.$geometry) {
  242. switch (val.$geometry.type) {
  243. case 'Polygon':
  244. case 'LineString':
  245. case 'Point':
  246. val.$geometry.coordinates.forEach(castArray);
  247. break;
  248. default:
  249. // ignore unknowns
  250. break;
  251. }
  252. }
  253. return val;
  254. }
  255. , '$geoIntersects': function (val) {
  256. var geo = val.$geometry;
  257. if (!geo) return;
  258. switch (val.$geometry.type) {
  259. case 'Polygon':
  260. case 'LineString':
  261. case 'Point':
  262. val.$geometry.coordinates.forEach(castArray);
  263. break;
  264. default:
  265. // ignore unknowns
  266. break;
  267. }
  268. return val;
  269. }
  270. , '$maxDistance': castToNumber
  271. };
  272. /*!
  273. * Module exports.
  274. */
  275. module.exports = SchemaArray;