/*! * Module dependencies. */ var SchemaType = require('../schematype') , CastError = SchemaType.CastError , NumberSchema = require('./number') , Types = { Boolean: require('./boolean') , Date: require('./date') , Number: require('./number') , String: require('./string') , ObjectId: require('./objectid') , Buffer: require('./buffer') } , MongooseArray = require('../types').Array , EmbeddedDoc = require('../types').Embedded , Mixed = require('./mixed') , Query = require('../query') , utils = require('../utils') , isMongooseObject = utils.isMongooseObject /** * Array SchemaType constructor * * @param {String} key * @param {SchemaType} cast * @param {Object} options * @inherits SchemaType * @api private */ function SchemaArray (key, cast, options) { if (cast) { var castOptions = {}; if ('Object' === cast.constructor.name) { if (cast.type) { // support { type: Woot } castOptions = utils.clone(cast); // do not alter user arguments delete castOptions.type; cast = cast.type; } else { cast = Mixed; } } // support { type: 'String' } var name = 'string' == typeof cast ? cast : cast.name; var caster = name in Types ? Types[name] : cast; this.casterConstructor = caster; this.caster = new caster(null, castOptions); if (!(this.caster instanceof EmbeddedDoc)) { this.caster.path = key; } } SchemaType.call(this, key, options); var self = this , defaultArr , fn; if (this.defaultValue) { defaultArr = this.defaultValue; fn = 'function' == typeof defaultArr; } this.default(function(){ var arr = fn ? defaultArr() : defaultArr || []; return new MongooseArray(arr, self.path, this); }); }; /*! * Inherits from SchemaType. */ SchemaArray.prototype.__proto__ = SchemaType.prototype; /** * Check required * * @param {Array} value * @api private */ SchemaArray.prototype.checkRequired = function (value) { return !!(value && value.length); }; /** * Overrides the getters application for the population special-case * * @param {Object} value * @param {Object} scope * @api private */ SchemaArray.prototype.applyGetters = function (value, scope) { if (this.caster.options && this.caster.options.ref) { // means the object id was populated return value; } return SchemaType.prototype.applyGetters.call(this, value, scope); }; /** * Casts contents * * @param {Object} value * @param {Document} doc document that triggers the casting * @param {Boolean} init whether this is an initialization cast * @api private */ SchemaArray.prototype.cast = function (value, doc, init) { if (Array.isArray(value)) { if (!(value instanceof MongooseArray)) { value = new MongooseArray(value, this.path, doc); } if (this.caster) { try { for (var i = 0, l = value.length; i < l; i++) { value[i] = this.caster.cast(value[i], doc, init); } } catch (e) { // rethrow throw new CastError(e.type, value, this.path); } } return value; } else { return this.cast([value], doc, init); } }; /** * Casts contents for queries. * * @param {String} $conditional * @param {any} [value] * @api private */ SchemaArray.prototype.castForQuery = function ($conditional, value) { var handler , val; if (arguments.length === 2) { handler = this.$conditionalHandlers[$conditional]; if (!handler) throw new Error("Can't use " + $conditional + " with Array."); val = handler.call(this, value); } else { val = $conditional; var proto = this.casterConstructor.prototype; var method = proto.castForQuery || proto.cast; var caster = this.caster; if (Array.isArray(val)) { val = val.map(function (v) { if (method) v = method.call(caster, v); return isMongooseObject(v) ? v.toObject() : v; }); } else if (method) { val = method.call(caster, val); } } return val && isMongooseObject(val) ? val.toObject() : val; }; /*! * @ignore */ function castToNumber (val) { return Types.Number.prototype.cast.call(this, val); } function castArray (arr, self) { self || (self = this); arr.forEach(function (v, i) { if (Array.isArray(v)) { castArray(v, self); } else { arr[i] = castToNumber.call(self, v); } }); } SchemaArray.prototype.$conditionalHandlers = { '$all': function handle$all (val) { if (!Array.isArray(val)) { val = [val]; } val = val.map(function (v) { if (v && 'Object' === v.constructor.name) { var o = {}; o[this.path] = v; var query = new Query(o); query.cast(this.casterConstructor); return query._conditions[this.path]; } return v; }, this); return this.castForQuery(val); } , '$elemMatch': function (val) { if (val.$in) { val.$in = this.castForQuery('$in', val.$in); return val; } var query = new Query(val); query.cast(this.casterConstructor); return query._conditions; } , '$size': castToNumber , '$ne': SchemaArray.prototype.castForQuery , '$in': SchemaArray.prototype.castForQuery , '$nin': SchemaArray.prototype.castForQuery , '$regex': SchemaArray.prototype.castForQuery , '$options': String , '$near': SchemaArray.prototype.castForQuery , '$nearSphere': SchemaArray.prototype.castForQuery , '$gt': SchemaArray.prototype.castForQuery , '$gte': SchemaArray.prototype.castForQuery , '$lt': SchemaArray.prototype.castForQuery , '$lte': SchemaArray.prototype.castForQuery , '$within': function (val) { var self = this; if (val.$maxDistance) { val.$maxDistance = castToNumber.call(this, val.$maxDistance); } if (val.$box || val.$polygon) { var type = val.$box ? '$box' : '$polygon'; val[type].forEach(function (arr) { if (!Array.isArray(arr)) { var msg = 'Invalid $within $box argument. ' + 'Expected an array, received ' + arr; throw new TypeError(msg); } arr.forEach(function (v, i) { arr[i] = castToNumber.call(this, v); }); }) } else if (val.$center || val.$centerSphere) { var type = val.$center ? '$center' : '$centerSphere'; val[type].forEach(function (item, i) { if (Array.isArray(item)) { item.forEach(function (v, j) { item[j] = castToNumber.call(this, v); }); } else { val[type][i] = castToNumber.call(this, item); } }) } else if (val.$geometry) { switch (val.$geometry.type) { case 'Polygon': case 'LineString': case 'Point': val.$geometry.coordinates.forEach(castArray); break; default: // ignore unknowns break; } } return val; } , '$geoIntersects': function (val) { var geo = val.$geometry; if (!geo) return; switch (val.$geometry.type) { case 'Polygon': case 'LineString': case 'Point': val.$geometry.coordinates.forEach(castArray); break; default: // ignore unknowns break; } return val; } , '$maxDistance': castToNumber }; /*! * Module exports. */ module.exports = SchemaArray;