Skip to content

Commit 80f8332

Browse files
committed
Merge branch 'master' into gh2719
2 parents 78f3ebb + a6a47cd commit 80f8332

File tree

13 files changed

+9140
-46908
lines changed

13 files changed

+9140
-46908
lines changed

bin/mongoose.js

Lines changed: 8960 additions & 46823 deletions
Large diffs are not rendered by default.

lib/document.js

Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ Document.prototype.set = function (path, val, type, options) {
513513
this.set(val, path, constructing);
514514
return this;
515515
}
516-
this.$__error(new MongooseError.CastError('Object', val, path));
516+
this.invalidate(path, new MongooseError.CastError('Object', val, path));
517517
return this;
518518
}
519519

@@ -593,6 +593,7 @@ Document.prototype.set = function (path, val, type, options) {
593593
this.populated(path, val._id);
594594
}
595595
val = schema.applySetters(val, this, false, priorVal);
596+
this.$markValid(path);
596597
} catch (e) {
597598
this.invalidate(e.path, new ValidatorError({
598599
path: e.path,
@@ -973,12 +974,6 @@ Document.prototype.validate = function (cb) {
973974
var self = this;
974975
var promise = new Promise(cb);
975976

976-
var preSaveErr = self.$__presaveValidate();
977-
if (preSaveErr) {
978-
promise.reject(preSaveErr);
979-
return promise;
980-
}
981-
982977
// only validate required fields when necessary
983978
var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) {
984979
if (!self.isSelected(path) && !self.isModified(path)) return false;
@@ -1024,6 +1019,10 @@ Document.prototype.validate = function (cb) {
10241019
process.nextTick(function(){
10251020
var p = self.schema.path(path);
10261021
if (!p) return --total || complete();
1022+
if (!self.$isValid(path)) {
1023+
--total || complete();
1024+
return;
1025+
}
10271026

10281027
var val = self.getValue(path);
10291028
p.doValidate(val, function (err) {
@@ -1089,10 +1088,13 @@ Document.prototype.validateSync = function () {
10891088

10901089
var p = self.schema.path(path);
10911090
if (!p) return;
1091+
if (!self.$isValid(path)) {
1092+
return;
1093+
}
10921094

10931095
var val = self.getValue(path);
1094-
var err = p.doValidateSync( val, self );
1095-
if ( err ){
1096+
var err = p.doValidateSync(val, self);
1097+
if (err) {
10961098
self.invalidate(path, err, undefined, true);
10971099
}
10981100
});
@@ -1152,7 +1154,36 @@ Document.prototype.invalidate = function (path, err, val) {
11521154
if (this.$__.validationError == err) return;
11531155

11541156
this.$__.validationError.errors[path] = err;
1155-
}
1157+
};
1158+
1159+
/**
1160+
* Marks a path as valid, removing existing validation errors.
1161+
*
1162+
* @param {String} path the field to mark as valid
1163+
* @api private
1164+
*/
1165+
1166+
Document.prototype.$markValid = function(path) {
1167+
if (!this.$__.validationError || !this.$__.validationError.errors[path]) {
1168+
return;
1169+
}
1170+
1171+
delete this.$__.validationError.errors[path];
1172+
if (Object.keys(this.$__.validationError.errors).length === 0) {
1173+
this.$__.validationError = null;
1174+
}
1175+
};
1176+
1177+
/**
1178+
* Checks if a path is invalid
1179+
*
1180+
* @param {String} path the field to check
1181+
* @api private
1182+
*/
1183+
1184+
Document.prototype.$isValid = function(path) {
1185+
return !this.$__.validationError || !this.$__.validationError.errors[path];
1186+
};
11561187

11571188
/**
11581189
* Resets the internal modified state of this document.
@@ -1442,46 +1473,6 @@ Document.prototype.$__getAllSubdocs = function () {
14421473
return subDocs;
14431474
};
14441475

1445-
1446-
/**
1447-
* Handle generic save stuff.
1448-
* to solve #1446 use use hierarchy instead of hooks
1449-
*
1450-
* @api private
1451-
* @method $__presaveValidate
1452-
* @memberOf Document
1453-
*/
1454-
1455-
Document.prototype.$__presaveValidate = function $__presaveValidate() {
1456-
// if any doc.set() calls failed
1457-
1458-
var docs = this.$__getArrayPathsToValidate();
1459-
1460-
var e2 = docs.map(function (doc) {
1461-
return doc.$__presaveValidate();
1462-
});
1463-
var e1 = [this.$__.saveError].concat(e2);
1464-
var err = e1.filter(function (x) {return x})[0];
1465-
this.$__.saveError = null;
1466-
1467-
return err;
1468-
};
1469-
1470-
1471-
/**
1472-
* Registers an error
1473-
*
1474-
* @param {Error} err
1475-
* @api private
1476-
* @method $__error
1477-
* @memberOf Document
1478-
*/
1479-
1480-
Document.prototype.$__error = function (err) {
1481-
this.$__.saveError = err;
1482-
return this;
1483-
};
1484-
14851476
/**
14861477
* Executes methods queued from the Schema definition
14871478
*

lib/drivers/node-mongodb-native/binary.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Module dependencies.
44
*/
55

6-
var Binary = require('mongodb/node_modules/mongodb-core/node_modules/bson').Binary;
6+
// Don't include all of BSON for browserify's benefit
7+
var Binary = require('mongodb/node_modules/mongodb-core/node_modules/bson/lib/bson/binary');
78

89
module.exports = exports = Binary;

lib/drivers/node-mongodb-native/objectid.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* @see ObjectId
66
*/
77

8-
var ObjectId = require('mongodb/node_modules/mongodb-core/node_modules/bson').ObjectID;
8+
// Don't include all of BSON for browserify's benefit
9+
var ObjectId = require('mongodb/node_modules/mongodb-core/node_modules/bson/lib/bson/objectid');
910

1011
/*!
1112
* ignore

lib/model.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -148,28 +148,6 @@ Model.prototype.$__handleSave = function $__handleSave(options) {
148148
return innerPromise;
149149
};
150150

151-
Model.prototype.$__preSave = function(next) {
152-
// Nested docs have their own presave
153-
if (this.ownerDocument) {
154-
return next();
155-
}
156-
157-
// Check for preSave errors if we're not validating
158-
if (!this.schema.options.validateBeforeSave) {
159-
var preSaveErr = this.$__presaveValidate();
160-
if (preSaveErr) {
161-
return next(preSaveErr);
162-
}
163-
}
164-
165-
// Validate
166-
if (this.schema.options.validateBeforeSave) {
167-
this.validate(next);
168-
} else {
169-
next();
170-
}
171-
};
172-
173151
/**
174152
* @description Saves this document.
175153
*
@@ -2267,6 +2245,8 @@ function populate(model, docs, options, cb) {
22672245
return undefined !== item;
22682246
});
22692247

2248+
ids = utils.array.unique(ids);
2249+
22702250
if (0 === ids.length || ids.every(utils.isNullOrUndefined)) {
22712251
return cb();
22722252
}

lib/schema.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,19 @@ function Schema (obj, options) {
103103
this.virtual('id').get(idGetter);
104104
}
105105

106-
this.pre('save', require('./model').prototype.$__preSave);
106+
this.pre('save', function(next) {
107+
// Nested docs have their own presave
108+
if (this.ownerDocument) {
109+
return next();
110+
}
111+
112+
// Validate
113+
if (this.schema.options.validateBeforeSave) {
114+
this.validate(next);
115+
} else {
116+
next();
117+
}
118+
});
107119

108120
// adds updatedAt and createdAt timestamps to documents if enabled
109121
var timestamps = this.options.timestamps;
@@ -293,8 +305,9 @@ Schema.prototype.add = function add (obj, prefix) {
293305

294306
Schema.reserved = Object.create(null);
295307
var reserved = Schema.reserved;
308+
reserved.emit =
296309
reserved.on =
297-
reserved.once =
310+
reserved.once = // EventEmitter
298311
reserved.db =
299312
reserved.set =
300313
reserved.get =
@@ -307,7 +320,7 @@ reserved.collection =
307320
reserved.toObject =
308321
reserved.save =
309322
reserved.validate =
310-
reserved.emit = // EventEmitter
323+
reserved.increment = // document properties and functions
311324
reserved._pres = reserved._posts = 1 // hooks.js
312325

313326
/**

lib/schema/buffer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
var utils = require('../utils');
66

77
var MongooseBuffer = require('../types').Buffer;
8-
var Query = require('../query');
98
var SchemaType = require('../schematype');
109

1110
var Binary = MongooseBuffer.Binary;

lib/types/embedded.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,46 @@ EmbeddedDocument.prototype.invalidate = function (path, err, val, first) {
184184
if (first)
185185
this.$__.validationError = this.ownerDocument().$__.validationError;
186186
return true;
187-
}
187+
};
188+
189+
/**
190+
* Marks a path as valid, causing validation to succeed
191+
*
192+
* @param {String} path the field to mark as valid
193+
* @api public
194+
*/
195+
EmbeddedDocument.prototype.$markValid = function(path) {
196+
if (!this.__parent) {
197+
return;
198+
}
199+
200+
var index = this.__parentArray.indexOf(this);
201+
var parentPath = this.__parentArray._path;
202+
var fullPath = [parentPath, index, path].join('.');
203+
204+
this.__parent.$markValid(fullPath);
205+
};
206+
207+
/**
208+
* Checks if a path is invalid
209+
*
210+
* @param {String} path the field to check
211+
* @api private
212+
*/
213+
214+
EmbeddedDocument.prototype.$isValid = function(path) {
215+
if (!this.__parent) {
216+
var msg = 'Unable to invalidate a subdocument that has not been added to an array.'
217+
throw new Error(msg);
218+
}
219+
220+
var index = this.__parentArray.indexOf(this);
221+
var parentPath = this.__parentArray._path;
222+
var fullPath = [parentPath, index, path].join('.');
223+
224+
return !this.__parent.$__.validationError ||
225+
!this.__parent.$__.validationError.errors[path];
226+
};
188227

189228
/**
190229
* Returns the top level document of this sub-document.

lib/utils.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,45 @@ exports.array.flatten = function flatten (arr, filter, ret) {
667667
});
668668

669669
return ret;
670-
}
670+
};
671+
672+
/*!
673+
* Removes duplicate values from an array
674+
*
675+
* [1, 2, 3, 3, 5] => [1, 2, 3, 5]
676+
* [ ObjectId("550988ba0c19d57f697dc45e"), ObjectId("550988ba0c19d57f697dc45e") ]
677+
* => [ObjectId("550988ba0c19d57f697dc45e")]
678+
*
679+
* @param {Array} arr
680+
* @return {Array}
681+
* @private
682+
*/
683+
684+
exports.array.unique = function(arr) {
685+
var primitives = {};
686+
var ids = {};
687+
var ret = [];
688+
var length = arr.length;
689+
for (var i = 0; i < length; ++i) {
690+
if (typeof arr[i] === 'number' || typeof arr[i] === 'string') {
691+
if (primitives[arr[i]]) {
692+
continue;
693+
}
694+
ret.push(arr[i]);
695+
primitives[arr[i]] = true;
696+
} else if (arr[i] instanceof ObjectId) {
697+
if (ids[arr[i].toString()]) {
698+
continue;
699+
}
700+
ret.push(arr[i]);
701+
ids[arr[i].toString()] = true;
702+
} else {
703+
ret.push(arr[i]);
704+
}
705+
}
706+
707+
return ret;
708+
};
671709

672710
/*!
673711
* Determines if two buffers are equal.

test/document.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,22 @@ describe('document', function(){
10331033
}, 500);
10341034
});
10351035

1036+
it('doesnt have stale cast errors (gh-2766)', function(done) {
1037+
var db = start();
1038+
var testSchema = new Schema({ name: String });
1039+
var M = db.model('gh2766', testSchema);
1040+
1041+
var m = new M({ _id: 'this is not a valid _id' });
1042+
assert.ok(!m.$isValid('_id'));
1043+
m._id = '000000000000000000000001';
1044+
assert.ok(m.$isValid('_id'));
1045+
m.validate(function(error) {
1046+
assert.ifError(error);
1047+
db.close(done);
1048+
1049+
});
1050+
});
1051+
10361052
it('returns a promise when there are no validators', function(done) {
10371053
var db = start();
10381054
var schema = null;

test/model.test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -856,16 +856,15 @@ describe('Model', function(){
856856
, BlogPost = db.model('BlogPost', collection)
857857
, threw = false;
858858

859-
var post = new BlogPost()
860-
post.init({
859+
var post = new BlogPost({
861860
title : 'Test'
862861
, slug : 'test'
863862
, comments : [ { title: 'Test', date: new Date, body: 'Test' } ]
864863
});
865864

866865
post.get('comments')[0].set('date', 'invalid');
867866

868-
post.save(function(err){
867+
post.save(function(err) {
869868
db.close();
870869
assert.ok(err instanceof MongooseError);
871870
assert.ok(err instanceof ValidationError);

test/schema.validation.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ describe('schema', function(){
779779
assert.ok(error);
780780
var errorMessage = 'CastError: Cast to Object failed for value ' +
781781
'"waffles" at path "foods"';
782-
assert.equal(errorMessage, error.toString());
782+
assert.ok(error.toString().indexOf(errorMessage) !== -1, error.toString());
783783
done();
784784
});
785785
});

0 commit comments

Comments
 (0)