Revision 121df320d337fd860fed17f9bbddeb8e605427e2 authored by Aaron Heckmann on 09 February 2012, 13:20:07 UTC, committed by Aaron Heckmann on 09 February 2012, 13:20:07 UTC
1 parent 627745b
Raw File
document.js
/**
 * Module dependencies.
 */

var EventEmitter = require('events').EventEmitter
  , MongooseError = require('./error')
  , MixedSchema = require('./schema/mixed')
  , Schema = require('./schema')
  , ValidatorError = require('./schematype').ValidatorError
  , utils = require('./utils')
  , clone = utils.clone
  , isMongooseObject = utils.isMongooseObject
  , inspect = require('util').inspect
  , StateMachine = require('./statemachine')
  , ActiveRoster = StateMachine.ctor('require', 'modify', 'init')
  , deepEqual = utils.deepEqual
  , hooks = require('hooks')
  , DocumentArray

/**
 * Document constructor.
 *
 * @param {Object} values to set
 * @api private
 */

function Document (obj, fields) {
  // node <0.4.3 bug
  if (!this._events) this._events = {};
  this.setMaxListeners(0);

  this._strictMode = this.schema.options && this.schema.options.strict;

  if ('boolean' === typeof fields) {
    this._strictMode = fields;
    fields = undefined;
  } else {
    this._selected = fields;
  }

  this._doc = this.buildDoc(fields);
  this._activePaths = new ActiveRoster();
  var self = this;
  this.schema.requiredPaths.forEach(function (path) {
    self._activePaths.require(path);
  });

  this._saveError = null;
  this._validationError = null;
  this.isNew = true;

  if (obj) this.set(obj, undefined, true);

  this._registerHooks();
  this.doQueue();

  this.errors = undefined;
  this._shardval = undefined;
};

/**
 * Inherit from EventEmitter.
 */

Document.prototype.__proto__ = EventEmitter.prototype;

/**
 * Base Mongoose instance for the model. Set by the Mongoose instance upon
 * pre-compilation.
 *
 * @api public
 */

Document.prototype.base;

/**
 * Document schema as a nested structure.
 *
 * @api public
 */

Document.prototype.schema;

/**
 * Whether the document is new.
 *
 * @api public
 */

Document.prototype.isNew;

/**
 * Validation errors.
 *
 * @api public
 */

Document.prototype.errors;

/**
 * Builds the default doc structure
 *
 * @api private
 */

Document.prototype.buildDoc = function (fields) {
  var doc = {}
    , self = this
    , exclude
    , keys
    , key
    , ki

  // determine if this doc is a result of a query with
  // excluded fields
  if (fields && 'Object' === fields.constructor.name) {
    keys = Object.keys(fields);
    ki = keys.length;

    while (ki--) {
      if ('_id' !== keys[ki]) {
        exclude = 0 === fields[keys[ki]];
        break;
      }
    }
  }

  var paths = Object.keys(this.schema.paths)
    , plen = paths.length
    , ii = 0

  for (; ii < plen; ++ii) {
    var p = paths[ii]
      , type = this.schema.paths[p]
      , path = p.split('.')
      , len = path.length
      , last = len-1
      , doc_ = doc
      , i = 0

    for (; i < len; ++i) {
      var piece = path[i]
        , def

      if (i === last) {
        if (fields) {
          if (exclude) {
            // apply defaults to all non-excluded fields
            if (p in fields) continue;

            def = type.getDefault(self);
            if ('undefined' !== typeof def) doc_[piece] = def;

          } else {
            // do nothing. only the fields specified in
            // the query will be populated
          }
        } else {
          def = type.getDefault(self);
          if ('undefined' !== typeof def) doc_[piece] = def;
        }
      } else {
        doc_ = doc_[piece] || (doc_[piece] = {});
      }
    }
  };

  return doc;
};

/**
 * Inits (hydrates) the document without setters.
 *
 * Called internally after a document is returned
 * from mongodb.
 *
 * @param {Object} document returned by mongo
 * @param {Function} callback
 * @api private
 */

Document.prototype.init = function (doc, fn) {
  this.isNew = false;

  init(this, doc, this._doc);
  this._storeShard();

  this.emit('init');
  if (fn) fn(null);
  return this;
};

/**
 * Init helper.
 * @param {Object} instance
 * @param {Object} obj - raw mongodb doc
 * @param {Object} doc - object we are initializing
 * @private
 */

function init (self, obj, doc, prefix) {
  prefix = prefix || '';

  var keys = Object.keys(obj)
    , len = keys.length
    , schema
    , path
    , i;

  while (len--) {
    i = keys[len];
    path = prefix + i;
    schema = self.schema.path(path);

    if (!schema && obj[i] && 'Object' === obj[i].constructor.name) {
      // assume nested object
      doc[i] = {};
      init(self, obj[i], doc[i], path + '.');
    } else {
      if (obj[i] === null) {
        doc[i] = null;
      } else if (obj[i] !== undefined) {
        if (schema) {
          self.try(function(){
            doc[i] = schema.cast(obj[i], self, true);
          });
        } else {
          doc[i] = obj[i];
        }
      }
      // mark as hydrated
      self._activePaths.init(path);
    }
  }
};

/**
 * _storeShard
 *
 * Stores the current values of the shard keys
 * for use later in the doc.save() where clause.
 *
 * Shard key values do not / are not allowed to change.
 *
 * @param {Object} document
 * @private
 */

Document.prototype._storeShard = function _storeShard () {
  var key = this.schema.options.shardkey;
  if (!(key && 'Object' == key.constructor.name)) return;

  var orig = this._shardval = {}
    , paths = Object.keys(key)
    , len = paths.length
    , val

  for (var i = 0; i < len; ++i) {
    val = this.getValue(paths[i]);
    if (isMongooseObject(val)) {
      orig[paths[i]] = val.toObject({ depopulate: true })
    } else if (val.valueOf) {
      orig[paths[i]] = val.valueOf();
    } else {
      orig[paths[i]] = val;
    }
  }
}

// Set up middleware support
for (var k in hooks) {
  Document.prototype[k] = Document[k] = hooks[k];
}

/**
 * Sets a path, or many paths
 *
 * Examples:
 *     // path, value
 *     doc.set(path, value)
 *
 *     // object
 *     doc.set({
 *         path  : value
 *       , path2 : {
 *            path  : value
 *         }
 *     }
 *
 * @param {String|Object} key path, or object
 * @param {Object} value, or undefined or a prefix if first parameter is an object
 * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute
 * @api public
 */

Document.prototype.set = function (path, val, type) {
  var constructing = true === type
    , adhoc = type && true !== type
    , adhocs

  if (adhoc) {
    adhocs = this._adhocPaths || (this._adhocPaths = {});
    adhocs[path] = Schema.interpretAsType(path, type);
  }

  if ('string' !== typeof path) {
    // new Document({ key: val })

    if (null === path || undefined === path) {
      var _ = path;
      path = val;
      val = _;

    } else {
      var prefix = val
        ? val + '.'
        : '';

      if (path instanceof Document) path = path._doc;

      var keys = Object.keys(path)
        , i = keys.length
        , pathtype
        , key

      while (i--) {
        key = keys[i];
        if (null != path[key] && 'Object' === path[key].constructor.name
          && !(this._path(prefix + key) instanceof MixedSchema)) {
          this.set(path[key], prefix + key, constructing);
        } else if (this._strictMode) {
          pathtype = this.schema.pathType(prefix + key);
          if ('real' === pathtype || 'virtual' === pathtype) {
            this.set(prefix + key, path[key], constructing);
          }
        } else if (undefined !== path[key]) {
          this.set(prefix + key, path[key], constructing);
        }
      }

      return this;
    }
  }

  var schema;
  if ('virtual' === this.schema.pathType(path)) {
    schema = this.schema.virtualpath(path);
    schema.applySetters(val, this);
    return this;
  } else {
    schema = this._path(path);
  }

  var parts = path.split('.')
    , obj = this._doc
    , self = this
    , pathToMark
    , subpaths
    , subpath

  // When using the $set operator the path to the field must already exist.
  // Else mongodb throws: "LEFT_SUBFIELD only supports Object"

  if (parts.length <= 1) {
    pathToMark = path;
  } else {
    subpaths = parts.map(function (part, i) {
      return parts.slice(0, i).concat(part).join('.');
    });

    for (var i = 0, l = subpaths.length; i < l; i++) {
      subpath = subpaths[i];
      if (this.isDirectModified(subpath) // earlier prefixes that are already
                                         // marked as dirty have precedence
          || this.get(subpath) === null) {
        pathToMark = subpath;
        break;
      }
    }

    if (!pathToMark) pathToMark = path;
  }

  if ((!schema || null === val || undefined === val) ||
    this.try(function(){
      // if this doc is being constructed we should not
      // trigger getters.
      var cur = constructing ? undefined : self.get(path);
      var casted = schema.cast(val, self, false, cur);
      val = schema.applySetters(casted, self);
    })) {

    if (this.isNew) {
      this.markModified(pathToMark);
    } else {
      var priorVal = this.get(path);
      if (!this.isDirectModified(pathToMark) && !deepEqual(val, priorVal)) {
        this.markModified(pathToMark);
      }
    }

    for (var i = 0, l = parts.length; i < l; i++) {
      var next = i + 1
        , last = next === l;

      if (last) {
        obj[parts[i]] = val;
      } else {
        if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) {
          obj = obj[parts[i]];
        } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) {
          obj = obj[parts[i]];
        } else {
          obj = obj[parts[i]] = {};
        }
      }
    }
  }

  return this;
};

/**
 * Gets a raw value from a path (no getters)
 *
 * @param {String} path
 * @api private
 */

Document.prototype.getValue = function (path) {
  var parts = path.split('.')
    , obj = this._doc
    , part;

  for (var i = 0, l = parts.length; i < l-1; i++) {
    part = parts[i];
    path = convertIfInt(path);
    obj = obj.getValue
        ? obj.getValue(part) // If we have an embedded array document member
        : obj[part];
    if (!obj) return obj;
  }

  part = parts[l-1];
  path = convertIfInt(path);

  return obj.getValue
    ? obj.getValue(part) // If we have an embedded array document member
    : obj[part];
};

function convertIfInt (string) {
  if (/^\d+$/.test(string)) {
    return parseInt(string, 10);
  }
  return string;
}

/**
 * Sets a raw value for a path (no casting, setters, transformations)
 *
 * @param {String} path
 * @param {Object} value
 * @api private
 */

Document.prototype.setValue = function (path, val) {
  var parts = path.split('.')
    , obj = this._doc;

  for (var i = 0, l = parts.length; i < l-1; i++) {
    obj = obj[parts[i]];
  }

  obj[parts[l-1]] = val;
  return this;
};

/**
 * Triggers casting on a specific path
 *
 * @todo - deprecate? not used anywhere
 * @param {String} path
 * @api public
 */

Document.prototype.doCast = function (path) {
  var schema = this.schema.path(path);
  if (schema)
    this.setValue(path, this.getValue(path));
};

/**
 * Gets a path
 *
 * @param {String} key path
 * @param @optional {Schema|String|...} specify a type if this is an on-the-fly attribute
 * @api public
 */

Document.prototype.get = function (path, type) {
  var adhocs;
  if (type) {
    adhocs = this._adhocPaths || (this._adhocPaths = {});
    adhocs[path] = Schema.interpretAsType(path, type);
  }

  var schema = this._path(path) || this.schema.virtualpath(path)
    , pieces = path.split('.')
    , obj = this._doc;

  for (var i = 0, l = pieces.length; i < l; i++) {
    obj = null == obj ? null : obj[pieces[i]];
  }

  if (schema) {
    obj = schema.applyGetters(obj, this);
  }

  return obj;
};

/**
 * Finds the path in the ad hoc type schema list or
 * in the schema's list of type schemas
 * @param {String} path
 * @api private
 */

Document.prototype._path = function (path) {
  var adhocs = this._adhocPaths
    , adhocType = adhocs && adhocs[path];

  if (adhocType) {
    return adhocType;
  } else {
    return this.schema.path(path);
  }
};

/**
 * Commits a path, marking as modified if needed. Useful for mixed keys
 *
 * @api public
 */

Document.prototype.commit =
Document.prototype.markModified = function (path) {
  this._activePaths.modify(path);
};

/**
 * Captures an exception that will be bubbled to `save`
 *
 * @param {Function} function to execute
 * @param {Object} scope
 */

Document.prototype.try = function (fn, scope) {
  var res;
  try {
    fn.call(scope);
    res = true;
  } catch (e) {
    this.error(e);
    res = false;
  }
  return res;
};

/**
 * modifiedPaths
 *
 * Returns the list of paths that have been modified.
 *
 * If we set `documents.0.title` to 'newTitle'
 * then `documents`, `documents.0`, and `documents.0.title`
 * are modified.
 *
 * @api public
 * @returns Boolean
 */

Document.prototype.__defineGetter__('modifiedPaths', function () {
  var directModifiedPaths = Object.keys(this._activePaths.states.modify);

  return directModifiedPaths.reduce(function (list, path) {
    var parts = path.split('.');
    return list.concat(parts.reduce(function (chains, part, i) {
      return chains.concat(parts.slice(0, i).concat(part).join('.'));
    }, []));
  }, []);
});

/**
 * Checks if a path or any full path containing path as part of
 * its path chain has been directly modified.
 *
 * e.g., if we set `documents.0.title` to 'newTitle'
 *       then we have directly modified `documents.0.title`
 *       but not directly modified `documents` or `documents.0`.
 *       Nonetheless, we still say `documents` and `documents.0`
 *       are modified. They just are not considered direct modified.
 *       The distinction is important because we need to distinguish
 *       between what has been directly modified and what hasn't so
 *       that we can determine the MINIMUM set of dirty data
 *       that we want to send to MongoDB on a Document save.
 *
 * @param {String} path
 * @returns Boolean
 * @api public
 */

Document.prototype.isModified = function (path) {
  return !!~this.modifiedPaths.indexOf(path);
};

/**
 * Checks if a path has been directly set and modified. False if
 * the path is only part of a larger path that was directly set.
 *
 * e.g., if we set `documents.0.title` to 'newTitle'
 *       then we have directly modified `documents.0.title`
 *       but not directly modified `documents` or `documents.0`.
 *       Nonetheless, we still say `documents` and `documents.0`
 *       are modified. They just are not considered direct modified.
 *       The distinction is important because we need to distinguish
 *       between what has been directly modified and what hasn't so
 *       that we can determine the MINIMUM set of dirty data
 *       that we want to send to MongoDB on a Document save.
 *
 * @param {String} path
 * @returns Boolean
 * @api public
 */

Document.prototype.isDirectModified = function (path) {
  return (path in this._activePaths.states.modify);
};

/**
 * Checks if a certain path was initialized
 *
 * @param {String} path
 * @returns Boolean
 * @api public
 */

Document.prototype.isInit = function (path) {
  return (path in this._activePaths.states.init);
};

/**
 * Checks if a path was selected.
 * @param {String} path
 * @return Boolean
 * @api public
 */

Document.prototype.isSelected = function isSelected (path) {
  if (this._selected) {

    if ('_id' === path) {
      return 0 !== this._selected._id;
    }

    var paths = Object.keys(this._selected)
      , i = paths.length
      , inclusive = false
      , cur

    while (i--) {
      cur = paths[i];
      if ('_id' == cur) continue;
      inclusive = !! this._selected[cur];
      break;
    }

    if (path in this._selected) {
      return inclusive;
    }

    i = paths.length;

    while (i--) {
      cur = paths[i];
      if ('_id' == cur) continue;

      if (0 === cur.indexOf(path + '.')) {
        return inclusive;
      }

      if (0 === (path + '.').indexOf(cur)) {
        return inclusive;
      }
    }

    return ! inclusive;
  }

  return true;
}

/**
 * Validation middleware
 *
 * @param {Function} next
 * @api public
 */

Document.prototype.validate = function (next) {
  var total = 0
    , self = this
    , validating = {}

  if (!this._activePaths.some('require', 'init', 'modify')) {
    return complete();
  }

  function complete () {
    next(self._validationError);
    self._validationError = null;
  }

  this._activePaths.forEach('require', 'init', 'modify', function validatePath (path) {
    if (validating[path]) return;

    validating[path] = true;
    total++;

    process.nextTick(function(){
      var p = self.schema.path(path);
      if (!p) return --total || complete();

      p.doValidate(self.getValue(path), function (err) {
        if (err) {
          self.invalidate(path, err);
        }
        --total || complete();
      }, self);
    });
  });

  return this;
};

/**
 * Marks a path as invalid, causing a subsequent validation to fail.
 *
 * @param {String} path of the field to invalidate
 * @param {String/Error} error of the path.
 * @api public
 */

Document.prototype.invalidate = function (path, err) {
  if (!this._validationError) {
    this._validationError = new ValidationError(this);
  }

  if (!err || 'string' === typeof err) {
    err = new ValidatorError(path, err);
  }

  this._validationError.errors[path] = err;
}

/**
 * Resets the atomics and modified states of this document.
 *
 * @private
 * @return {this}
 */

Document.prototype._reset = function reset () {
  var self = this;
  DocumentArray || (DocumentArray = require('./types/documentarray'));

  this._activePaths
  .map('init', 'modify', function (i) {
    return self.getValue(i);
  })
  .filter(function (val) {
    return (val && val instanceof DocumentArray && val.length);
  })
  .forEach(function (array) {
    array.forEach(function (doc) {
      doc._reset();
    });
  });

  // clear atomics
  this._dirty().forEach(function (dirt) {
    var type = dirt.value;
    if (type && type._path && type.doAtomics) {
      type._atomics = {};
    }
  });

  // Clear 'modify'('dirty') cache
  this._activePaths.clear('modify');
  var self = this;
  this.schema.requiredPaths.forEach(function (path) {
    self._activePaths.require(path);
  });

  return this;
}

/**
 * Returns the dirty paths / vals
 *
 * @api private
 */

Document.prototype._dirty = function _dirty () {
  var self = this;

  var all = this._activePaths.map('modify', function (path) {
    return { path: path
           , value: self.getValue(path)
           , schema: self._path(path) };
  });

  // Sort dirty paths in a flat hierarchy.
  all.sort(function (a, b) {
    return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
  });

  // Ignore "foo.a" if "foo" is dirty already.
  var minimal = []
    , lastReference = null;

  all.forEach(function (item, i) {
    if (item.path.indexOf(lastReference) !== 0) {
      lastReference = item.path + '.';
      minimal.push(item);
    }
  });

  return minimal;
}

/**
 * Returns if the document has been modified
 *
 * @return {Boolean}
 * @api public
 */

Document.prototype.__defineGetter__('modified', function () {
  return this._activePaths.some('modify');
});

/**
 * Compiles schemas.
 * @api private
 */

function compile (tree, proto, prefix) {
  var keys = Object.keys(tree)
    , i = keys.length
    , limb
    , key;

  while (i--) {
    key = keys[i];
    limb = tree[key];

    define(key
        , (('Object' === limb.constructor.name
               && Object.keys(limb).length)
               && (!limb.type || limb.type.type)
               ? limb
               : null)
        , proto
        , prefix
        , keys);
  }
};

/**
 * Defines the accessor named prop on the incoming prototype.
 * @api private
 */

function define (prop, subprops, prototype, prefix, keys) {
  var prefix = prefix || ''
    , path = (prefix ? prefix + '.' : '') + prop;

  if (subprops) {

    Object.defineProperty(prototype, prop, {
        enumerable: true
      , get: function () {
          if (!this.__getters)
            this.__getters = {};

          if (!this.__getters[path]) {
            var nested = Object.create(this);

            // save scope for nested getters/setters
            if (!prefix) nested._scope = this;

            // shadow inherited getters from sub-objects so
            // thing.nested.nested.nested... doesn't occur (gh-366)
            var i = 0
              , len = keys.length;

            for (; i < len; ++i) {
              // over-write the parents getter without triggering it
              Object.defineProperty(nested, keys[i], {
                  enumerable: false   // It doesn't show up.
                , writable: true      // We can set it later.
                , configurable: true  // We can Object.defineProperty again.
                , value: undefined    // It shadows its parent.
              });
            }

            nested.toObject = function () {
              return this.get(path);
            };

            compile(subprops, nested, path);
            this.__getters[path] = nested;
          }

          return this.__getters[path];
        }
      , set: function (v) {
          return this.set(v, path);
        }
    });

  } else {

    Object.defineProperty(prototype, prop, {
        enumerable: true
      , get: function ( ) { return this.get.call(this._scope || this, path); }
      , set: function (v) { return this.set.call(this._scope || this, path, v); }
    });
  }
};

/**
 * We override the schema setter to compile accessors
 *
 * @api private
 */

Document.prototype.__defineSetter__('schema', function (schema) {
  compile(schema.tree, this);
  this._schema = schema;
});

/**
 * We override the schema getter to return the internal reference
 *
 * @api private
 */

Document.prototype.__defineGetter__('schema', function () {
  return this._schema;
});

/**
 * Register default hooks
 *
 * @api private
 */

Document.prototype._registerHooks = function _registerHooks () {
  if (!this.save) return;

  DocumentArray || (DocumentArray = require('./types/documentarray'));

  this.pre('save', function (next) {
    // we keep the error semaphore to make sure we don't
    // call `save` unnecessarily (we only need 1 error)
    var subdocs = 0
      , error = false
      , self = this;

    var arrays = this._activePaths
    .map('init', 'modify', function (i) {
      return self.getValue(i);
    })
    .filter(function (val) {
      return (val && val instanceof DocumentArray && val.length);
    });

    if (!arrays.length)
      return next();

    arrays.forEach(function (array) {
      subdocs += array.length;
      array.forEach(function (value) {
        if (!error)
          value.save(function (err) {
            if (!error) {
              if (err) {
                error = true;
                next(err);
              } else
                --subdocs || next();
            }
          });
      });
    });
  }, function (err) {
    this.db.emit('error', err);
  }).pre('save', function checkForExistingErrors (next) {
    if (this._saveError) {
      next(this._saveError);
      this._saveError = null;
    } else {
      next();
    }
  }).pre('save', function validation (next) {
    return this.validate(next);
  });
};

/**
 * Registers an error
 *
 * @TODO underscore this method
 * @param {Error} error
 * @api private
 */

Document.prototype.error = function (err) {
  this._saveError = err;
  return this;
};

/**
 * Executes methods queued from the Schema definition
 *
 * @TODO underscore this method
 * @api private
 */

Document.prototype.doQueue = function () {
  if (this.schema && this.schema.callQueue)
    for (var i = 0, l = this.schema.callQueue.length; i < l; i++) {
      this[this.schema.callQueue[i][0]].apply(this, this.schema.callQueue[i][1]);
    }
  return this;
};

/**
 * Gets the document
 *
 * Available options:
 *
 * - getters: apply all getters (path and virtual getters)
 * - virtuals: apply virtual getters (can override `getters` option)
 *
 * Example of only applying path getters:
 *
 *     doc.toObject({ getters: true, virtuals: false })
 *
 * Example of only applying virtual getters:
 *
 *     doc.toObject({ virtuals: true })
 *
 * Example of applying both path and virtual getters:
 *
 *     doc.toObject({ getters: true })
 *
 * @return {Object} plain object
 * @api public
 */

Document.prototype.toObject = function (options) {
  options || (options = {});
  options.minimize = true;

  var ret = clone(this._doc, options);

  if (options.virtuals || options.getters && false !== options.virtuals) {
    applyGetters(this, ret, 'virtuals');
  }

  if (options.getters) {
    applyGetters(this, ret, 'paths');
  }

  return ret;
};

/**
 * Applies virtuals properties to `json`.
 *
 * @param {Document} self
 * @param {Object} json
 * @param {String} either `virtuals` or `paths`
 * @return json
 * @private
 */

function applyGetters (self, json, type) {
  var schema = self.schema
    , paths = Object.keys(schema[type])
    , i = paths.length
    , path

  while (i--) {
    path = paths[i];

    var parts = path.split('.')
      , plen = parts.length
      , last = plen - 1
      , branch = json
      , part

    for (var ii = 0; ii < plen; ++ii) {
      part = parts[ii];
      if (ii === last) {
        branch[part] = self.get(path);
      } else {
        branch = branch[part] || (branch[part] = {});
      }
    }
  }

  return json;
}

/**
 * JSON.stringify helper.
 *
 * Implicitly called when a document is passed
 * to JSON.stringify()
 *
 * @return {Object}
 * @api public
 */

Document.prototype.toJSON = function (options) {
  if ('undefined' === typeof options) options = {};
  options.json = true;
  return this.toObject(options);
};

/**
 * Helper for console.log
 *
 * @api public
 */

Document.prototype.toString =
Document.prototype.inspect = function (options) {
  return inspect(this.toObject(options));
};

/**
 * Returns true if the Document stores the same data as doc.
 * @param {Document} doc to compare to
 * @return {Boolean}
 * @api public
 */

Document.prototype.equals = function (doc) {
  return this.get('_id') === doc.get('_id');
};

/**
 * Module exports.
 */

module.exports = Document;

/**
 * Document Validation Error
 */

function ValidationError (instance) {
  MongooseError.call(this, "Validation failed");
  Error.captureStackTrace(this, arguments.callee);
  this.name = 'ValidationError';
  this.errors = instance.errors = {};
};

ValidationError.prototype.toString = function () {
  return this.name + ': ' + Object.keys(this.errors).map(function (key) {
    return String(this.errors[key]);
  }, this).join(', ');
};

/**
 * Inherits from MongooseError.
 */

ValidationError.prototype.__proto__ = MongooseError.prototype;

Document.ValidationError = ValidationError;

/**
 * Document Error
 *
 * @param text
 */

function DocumentError () {
  MongooseError.call(this, msg);
  Error.captureStackTrace(this, arguments.callee);
  this.name = 'DocumentError';
};

/**
 * Inherits from MongooseError.
 */

DocumentError.prototype.__proto__ = MongooseError.prototype;

exports.Error = DocumentError;
back to top