diff --git a/lib/helpers/setDefaultsOnInsert.js b/lib/helpers/setDefaultsOnInsert.js index 0545e80d3d4..747cb61ab8d 100644 --- a/lib/helpers/setDefaultsOnInsert.js +++ b/lib/helpers/setDefaultsOnInsert.js @@ -14,6 +14,17 @@ const modifiedPaths = require('./common').modifiedPaths; */ module.exports = function(filter, schema, castedDoc, options) { + options = options || {}; + + const shouldSetDefaultsOnInsert = + options.hasOwnProperty('setDefaultsOnInsert') ? + options.setDefaultsOnInsert : + schema.base.options.setDefaultsOnInsert; + + if (!options.upsert || !shouldSetDefaultsOnInsert) { + return castedDoc; + } + const keys = Object.keys(castedDoc || {}); const updatedKeys = {}; const updatedValues = {}; @@ -22,12 +33,6 @@ module.exports = function(filter, schema, castedDoc, options) { let hasDollarUpdate = false; - options = options || {}; - - if (!options.upsert || !options.setDefaultsOnInsert) { - return castedDoc; - } - for (let i = 0; i < numKeys; ++i) { if (keys[i].startsWith('$')) { modifiedPaths(castedDoc[keys[i]], '', modified); diff --git a/lib/index.js b/lib/index.js index 539e4b49d59..a72fdddffc6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -160,6 +160,7 @@ Mongoose.prototype.driver = require('./driver'); * - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject) * - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()` * - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas. + * - 'strictQuery': false by default, may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas. * - 'selectPopulatedPaths': true by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one. * - 'typePojoToMixed': true by default, may be `false` or `true`. Sets the default typePojoToMixed for schemas. * - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query diff --git a/lib/schema.js b/lib/schema.js index 062b5be8321..1724dd08be9 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -400,6 +400,7 @@ Schema.prototype.defaultOptions = function(options) { const baseOptions = get(this, 'base.options', {}); options = utils.options({ strict: 'strict' in baseOptions ? baseOptions.strict : true, + strictQuery: 'strictQuery' in baseOptions ? baseOptions.strictQuery : false, bufferCommands: true, capped: false, // { size, max, autoIndexId } versionKey: '__v', diff --git a/lib/validoptions.js b/lib/validoptions.js index 9464e4d8611..a9c8a352d23 100644 --- a/lib/validoptions.js +++ b/lib/validoptions.js @@ -16,7 +16,9 @@ const VALID_OPTIONS = Object.freeze([ 'objectIdGetter', 'runValidators', 'selectPopulatedPaths', + 'setDefaultsOnInsert', 'strict', + 'strictQuery', 'toJSON', 'toObject', 'useCreateIndex', diff --git a/test/index.test.js b/test/index.test.js index a8025689c86..bb9e070c74a 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -778,5 +778,57 @@ describe('mongoose module:', function() { done(); }); }); + + it('can set `setDefaultsOnInsert` as a global option (gh-9032)', function() { + return co(function* () { + const m = new mongoose.Mongoose(); + m.set('setDefaultsOnInsert', true); + const db = yield m.connect('mongodb://localhost:27017/mongoose_test_9032'); + + const schema = new m.Schema({ + title: String, + genre: { type: String, default: 'Action' } + }, { collection: 'movies_1' }); + + const Movie = db.model('Movie', schema); + + + yield Movie.updateOne( + {}, + { title: 'Cloud Atlas' }, + { upsert: true } + ); + + // lean is necessary to avoid defaults by casting + const movie = yield Movie.findOne({ title: 'Cloud Atlas' }).lean(); + assert.equal(movie.genre, 'Action'); + }); + }); + + it('setting `setDefaultOnInsert` on operation has priority over base option (gh-9032)', function() { + return co(function* () { + const m = new mongoose.Mongoose(); + m.set('setDefaultsOnInsert', true); + const db = yield m.connect('mongodb://localhost:27017/mongoose_test_9032'); + + const schema = new m.Schema({ + title: String, + genre: { type: String, default: 'Action' } + }, { collection: 'movies_2' }); + + const Movie = db.model('Movie', schema); + + + yield Movie.updateOne( + {}, + { title: 'The Man From Earth' }, + { upsert: true, setDefaultsOnInsert: false } + ); + + // lean is necessary to avoid defaults by casting + const movie = yield Movie.findOne({ title: 'The Man From Earth' }).lean(); + assert.ok(!movie.genre); + }); + }); }); }); diff --git a/test/schema.test.js b/test/schema.test.js index b01d5f7d248..bbb82886605 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2425,6 +2425,35 @@ describe('schema', function() { }); }); + describe('mongoose.set(`strictQuery`, value); (gh-6658)', function() { + let strictQueryOriginalValue; + + this.beforeEach(() => strictQueryOriginalValue = mongoose.get('strictQuery')); + this.afterEach(() => mongoose.set('strictQuery', strictQueryOriginalValue)); + + it('setting `strictQuery` on base sets strictQuery to schema (gh-6658)', function() { + // Arrange + mongoose.set('strictQuery', 'some value'); + + // Act + const schema = new Schema(); + + // Assert + assert.equal(schema.get('strictQuery'), 'some value'); + }); + + it('`strictQuery` set on base gets overwritten by option set on schema (gh-6658)', function() { + // Arrange + mongoose.set('strictQuery', 'base option'); + + // Act + const schema = new Schema({}, { strictQuery: 'schema option' }); + + // Assert + assert.equal(schema.get('strictQuery'), 'schema option'); + }); + }); + it('treats dotted paths with no parent as a nested path (gh-9020)', function() { const customerSchema = new Schema({ 'card.brand': String,