Skip to content

Commit

Permalink
Merge pull request #5748 from Automattic/5337
Browse files Browse the repository at this point in the history
feat(model): report validation errors from `insertMany()` if using `ordered: false` and `rawResult: true`
  • Loading branch information
vkarpov15 committed Oct 30, 2017
2 parents 01bf209 + 8632e79 commit a980af8
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
36 changes: 28 additions & 8 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2079,6 +2079,11 @@ var INSERT_MANY_CONVERT_OPTIONS = {
* because it only sends one operation to the server, rather than one for each
* document.
*
* Mongoose always validates each document **before** sending `insertMany`
* to MongoDB. So if one document has a validation error, no documents will
* be saved, unless you set
* [the `ordered` option to false](https://docs.mongodb.com/manual/reference/method/db.collection.insertMany/#error-handling).
*
* This function does **not** trigger save middleware.
*
* This function triggers the following middleware:
Expand All @@ -2091,6 +2096,8 @@ var INSERT_MANY_CONVERT_OPTIONS = {
*
* @param {Array|Object|*} doc(s)
* @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#insertMany)
* @param {Boolean} [options.ordered = true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
* @param {Boolean} [options.rawResult = false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `false`, will return the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~insertWriteOpCallback) with a `mongoose` property that contains `validationErrors` if this is an unordered `insertMany`.
* @param {Function} [callback] callback
* @return {Promise}
* @api public
Expand All @@ -2105,16 +2112,18 @@ Model.insertMany = function(arr, options, callback) {
if (callback) {
callback = this.$wrapCallback(callback);
}
var limit = options && options.limit;
if (typeof limit !== 'number') {
limit = 1000;
}
callback = callback || utils.noop;
options = options || {};
var limit = get(options, 'limit', 1000);
var rawResult = get(options, 'rawResult', false);
var ordered = get(options, 'ordered', true);

if (!Array.isArray(arr)) {
arr = [arr];
}

var toExecute = [];
var validationErrors = [];
arr.forEach(function(doc) {
toExecute.push(function(callback) {
doc = new _this(doc);
Expand All @@ -2123,7 +2132,8 @@ Model.insertMany = function(arr, options, callback) {
// Option `ordered` signals that insert should be continued after reaching
// a failing insert. Therefore we delegate "null", meaning the validation
// failed. It's up to the next function to filter out all failed models
if (options != null && typeof options === 'object' && options['ordered'] === false) {
if (ordered === false) {
validationErrors.push(error);
return callback(null, null);
}
return callback(error);
Expand All @@ -2144,7 +2154,7 @@ Model.insertMany = function(arr, options, callback) {
});
// Quickly escape while there aren't any valid docAttributes
if (docAttributes.length < 1) {
callback && callback(null, []);
callback(null, []);
return;
}
var docObjects = docAttributes.map(function(doc) {
Expand All @@ -2157,7 +2167,7 @@ Model.insertMany = function(arr, options, callback) {
return doc.toObject(INSERT_MANY_CONVERT_OPTIONS);
});

_this.collection.insertMany(docObjects, options, function(error) {
_this.collection.insertMany(docObjects, options, function(error, res) {
if (error) {
callback && callback(error);
return;
Expand All @@ -2167,7 +2177,17 @@ Model.insertMany = function(arr, options, callback) {
docAttributes[i].emit('isNew', false);
docAttributes[i].constructor.emit('isNew', false);
}
callback && callback(null, docAttributes);
if (rawResult) {
if (ordered === false) {
// Decorate with mongoose validation errors in case of unordered,
// because then still do `insertMany()`
res.mongoose = {
validationErrors: validationErrors
};
}
return callback(null, res);
}
callback(null, docAttributes);
});
});
};
Expand Down
6 changes: 6 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -894,3 +894,9 @@ exports.each = function(arr, fn) {
fn(arr[i]);
}
};

/*!
* ignore
*/

exports.noop = function() {};
22 changes: 22 additions & 0 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5398,6 +5398,28 @@ describe('Model', function() {
});
});

it('insertMany() multi validation error with ordered false (gh-5337)', function(done) {
var schema = new Schema({
name: { type: String, required: true }
});
var Movie = db.model('gh5337', schema);

var arr = [
{ foo: 'The Phantom Menace' },
{ name: 'Star Wars' },
{ name: 'The Empire Strikes Back' },
{ foobar: 'The Force Awakens' }
];
var opts = { ordered: false, rawResult: true };
Movie.insertMany(arr, opts, function(error, res) {
assert.ifError(error);
assert.equal(res.mongoose.validationErrors.length, 2);
assert.equal(res.mongoose.validationErrors[0].name, 'ValidationError');
assert.equal(res.mongoose.validationErrors[1].name, 'ValidationError');
done();
});
});

it('insertMany() depopulate (gh-4590)', function(done) {
var personSchema = new Schema({
name: String
Expand Down

0 comments on commit a980af8

Please sign in to comment.