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.
 
 
 
 
 

294 lines
7.8 KiB

/*!
* Module dependencies.
*/
var NodeJSDocument = require('./document');
var EventEmitter = require('events').EventEmitter;
var MongooseError = require('./error');
var Schema = require('./schema');
var ObjectId = require('./types/objectid');
var utils = require('./utils');
var ValidationError = MongooseError.ValidationError;
var InternalCache = require('./internal');
var PromiseProvider = require('./promise_provider');
var VersionError = require('./error').VersionError;
var Embedded;
/**
* Document constructor.
*
* @param {Object} obj the values to set
* @param {Object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
* @param {Boolean} [skipId] bool, should we auto create an ObjectId _id
* @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter
* @event `init`: Emitted on a document after it has was retrieved from the db and fully hydrated by Mongoose.
* @event `save`: Emitted when the document is successfully saved
* @api private
*/
function Document(obj, schema, fields, skipId, skipInit) {
if (!(this instanceof Document)) {
return new Document(obj, schema, fields, skipId, skipInit);
}
if (utils.isObject(schema) && !schema.instanceOfSchema) {
schema = new Schema(schema);
}
// When creating EmbeddedDocument, it already has the schema and he doesn't need the _id
schema = this.schema || schema;
// Generate ObjectId if it is missing, but it requires a scheme
if (!this.schema && schema.options._id) {
obj = obj || {};
if (obj._id === undefined) {
obj._id = new ObjectId();
}
}
if (!schema) {
throw new MongooseError.MissingSchemaError();
}
this.$__setSchema(schema);
this.$__ = new InternalCache;
this.$__.emitter = new EventEmitter();
this.isNew = true;
this.errors = undefined;
if (typeof fields === 'boolean') {
this.$__.strictMode = fields;
fields = undefined;
} else {
this.$__.strictMode = this.schema.options && this.schema.options.strict;
this.$__.selected = fields;
}
var required = this.schema.requiredPaths();
for (var i = 0; i < required.length; ++i) {
this.$__.activePaths.require(required[i]);
}
this.$__.emitter.setMaxListeners(0);
this._doc = this.$__buildDoc(obj, fields, skipId);
if (!skipInit && obj) {
this.init(obj);
}
this.$__registerHooksFromSchema();
// apply methods
for (var m in schema.methods) {
this[m] = schema.methods[m];
}
// apply statics
for (var s in schema.statics) {
this[s] = schema.statics[s];
}
}
/*!
* Inherit from the NodeJS document
*/
Document.prototype = Object.create(NodeJSDocument.prototype);
Document.prototype.constructor = Document;
/*!
* Browser doc exposes the event emitter API
*/
Document.$emitter = new EventEmitter();
utils.each(
['on', 'once', 'emit', 'listeners', 'removeListener', 'setMaxListeners',
'removeAllListeners', 'addListener'],
function(emitterFn) {
Document[emitterFn] = function() {
return Document.$emitter[emitterFn].apply(Document.$emitter, arguments);
};
});
/*!
* Executes methods queued from the Schema definition
*
* @api private
* @method $__registerHooksFromSchema
* @deprecated
* @memberOf Document
*/
Document.prototype.$__registerHooksFromSchema = function() {
Embedded = Embedded || require('./types/embedded');
var Promise = PromiseProvider.get();
var _this = this;
var q = _this.schema && _this.schema.callQueue;
var toWrapEl;
var len;
var i;
var j;
var pointCut;
var keys;
if (!q.length) {
return _this;
}
// we are only interested in 'pre' hooks, and group by point-cut
var toWrap = { post: [] };
var pair;
for (i = 0; i < q.length; ++i) {
pair = q[i];
if (pair[0] !== 'pre' && pair[0] !== 'post' && pair[0] !== 'on') {
_this[pair[0]].apply(_this, pair[1]);
continue;
}
var args = [].slice.call(pair[1]);
pointCut = pair[0] === 'on' ? 'post' : args[0];
if (!(pointCut in toWrap)) {
toWrap[pointCut] = {post: [], pre: []};
}
if (pair[0] === 'post') {
toWrap[pointCut].post.push(args);
} else if (pair[0] === 'on') {
toWrap[pointCut].push(args);
} else {
toWrap[pointCut].pre.push(args);
}
}
// 'post' hooks are simpler
len = toWrap.post.length;
toWrap.post.forEach(function(args) {
_this.on.apply(_this, args);
});
delete toWrap.post;
// 'init' should be synchronous on subdocuments
if (toWrap.init && _this instanceof Embedded) {
if (toWrap.init.pre) {
toWrap.init.pre.forEach(function(args) {
_this.$pre.apply(_this, args);
});
}
if (toWrap.init.post) {
toWrap.init.post.forEach(function(args) {
_this.$post.apply(_this, args);
});
}
delete toWrap.init;
} else if (toWrap.set) {
// Set hooks also need to be sync re: gh-3479
if (toWrap.set.pre) {
toWrap.set.pre.forEach(function(args) {
_this.$pre.apply(_this, args);
});
}
if (toWrap.set.post) {
toWrap.set.post.forEach(function(args) {
_this.$post.apply(_this, args);
});
}
delete toWrap.set;
}
keys = Object.keys(toWrap);
len = keys.length;
for (i = 0; i < len; ++i) {
pointCut = keys[i];
// this is so we can wrap everything into a promise;
var newName = ('$__original_' + pointCut);
if (!_this[pointCut]) {
continue;
}
if (_this[pointCut].$isWrapped) {
continue;
}
_this[newName] = _this[pointCut];
_this[pointCut] = (function(_newName) {
return function wrappedPointCut() {
var args = [].slice.call(arguments);
var lastArg = args.pop();
var fn;
var originalError = new Error();
var $results;
if (lastArg && typeof lastArg !== 'function') {
args.push(lastArg);
} else {
fn = lastArg;
}
var promise = new Promise.ES6(function(resolve, reject) {
args.push(function(error) {
if (error) {
// gh-2633: since VersionError is very generic, take the
// stack trace of the original save() function call rather
// than the async trace
if (error instanceof VersionError) {
error.stack = originalError.stack;
}
_this.$__handleReject(error);
reject(error);
return;
}
// There may be multiple results and promise libs other than
// mpromise don't support passing multiple values to `resolve()`
$results = Array.prototype.slice.call(arguments, 1);
resolve.apply(promise, $results);
});
_this[_newName].apply(_this, args);
});
if (fn) {
if (_this.constructor.$wrapCallback) {
fn = _this.constructor.$wrapCallback(fn);
}
return promise.then(
function() {
process.nextTick(function() {
fn.apply(null, [null].concat($results));
});
},
function(error) {
process.nextTick(function() {
fn(error);
});
});
}
return promise;
};
})(newName);
_this[pointCut].$isWrapped = true;
toWrapEl = toWrap[pointCut];
var _len = toWrapEl.pre.length;
args;
for (j = 0; j < _len; ++j) {
args = toWrapEl.pre[j];
args[0] = newName;
_this.$pre.apply(_this, args);
}
_len = toWrapEl.post.length;
for (j = 0; j < _len; ++j) {
args = toWrapEl.post[j];
args[0] = newName;
_this.$post.apply(_this, args);
}
}
return _this;
};
/*!
* Module exports.
*/
Document.ValidationError = ValidationError;
module.exports = exports = Document;