diff --git a/lib/index.js b/lib/index.js index a72fdddffc6..6a3e5ea2674 100644 --- a/lib/index.js +++ b/lib/index.js @@ -147,6 +147,7 @@ Mongoose.prototype.driver = require('./driver'); * * Currently supported options are: * - 'debug': If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arugments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`. + * - 'returnOriginal': If `false`, changes the default `returnOriginal` option to `findOneAndUpdate()`, `findByIdAndUpdate`, and `findOneAndReplace()` to false. This is equivalent to setting the `new` option to `true` for `findOneAndX()` calls by default. Read our [`findOneAndUpdate()` tutorial](/docs/tutorials/findoneandupdate.html) for more information. * - 'bufferCommands': enable/disable mongoose's buffering mechanism for all connections and models * - 'useCreateIndex': false by default. Set to `true` to make Mongoose's default index build use `createIndex()` instead of `ensureIndex()` to avoid deprecation warnings from the MongoDB driver. * - 'useFindAndModify': true by default. Set to `false` to make `findOneAndUpdate()` and `findOneAndRemove()` use native `findOneAndUpdate()` rather than `findAndModify()`. diff --git a/lib/model.js b/lib/model.js index a11e7021a5e..b469255930b 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2445,7 +2445,7 @@ Model.$where = function $where() { * @param {Object} [conditions] * @param {Object} [update] * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) - * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. + * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) @@ -2588,7 +2588,7 @@ function _decorateUpdateWithVersionKey(update, options, versionKey) { * @param {Object|Number|String} id value of `_id` to query by * @param {Object} [update] * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) - * @param {Boolean} [options.new=false] By default, `findByIdAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. + * @param {Boolean} [options.new=false] By default, `findByIdAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) @@ -2787,7 +2787,7 @@ Model.findByIdAndDelete = function(id, options, callback) { * @param {Object} filter Replace the first document that matches this filter * @param {Object} [replacement] Replace with this document * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) - * @param {Boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. + * @param {Boolean} [options.new=false] By default, `findOneAndReplace()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndReplace()` will instead give you the object after `update` was applied. To change the default to `true`, use `mongoose.set('returnOriginal', false);`. * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](/docs/api.html#query_Query-lean) and [the Mongoose lean tutorial](/docs/tutorials/lean.html). * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](/docs/transactions.html). * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) diff --git a/lib/query.js b/lib/query.js index 3cc41c64562..4bca9b90623 100644 --- a/lib/query.js +++ b/lib/query.js @@ -3004,20 +3004,25 @@ Query.prototype.findOneAndUpdate = function(criteria, doc, options, callback) { this._mergeUpdate(doc); } - if (options) { - options = utils.clone(options); - if (options.projection) { - this.select(options.projection); - delete options.projection; - } - if (options.fields) { - this.select(options.fields); - delete options.fields; - } + options = options ? utils.clone(options) : {}; - this.setOptions(options); + if (options.projection) { + this.select(options.projection); + delete options.projection; + } + if (options.fields) { + this.select(options.fields); + delete options.fields; + } + + + const returnOriginal = get(this, 'model.base.options.returnOriginal'); + if (options.returnOriginal == null && returnOriginal != null) { + options.returnOriginal = returnOriginal; } + this.setOptions(options); + if (!callback) { return this; } @@ -3335,7 +3340,14 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options, callb this._mergeUpdate(replacement); } - options && this.setOptions(options); + options = options || {}; + + const returnOriginal = get(this, 'model.base.options.returnOriginal'); + if (options.returnOriginal == null && returnOriginal != null) { + options.returnOriginal = returnOriginal; + } + + this.setOptions(options); if (!callback) { return this; diff --git a/lib/validoptions.js b/lib/validoptions.js index a9c8a352d23..6e50ec6b69d 100644 --- a/lib/validoptions.js +++ b/lib/validoptions.js @@ -14,6 +14,7 @@ const VALID_OPTIONS = Object.freeze([ 'debug', 'maxTimeMS', 'objectIdGetter', + 'returnOriginal', 'runValidators', 'selectPopulatedPaths', 'setDefaultsOnInsert', @@ -21,12 +22,12 @@ const VALID_OPTIONS = Object.freeze([ 'strictQuery', 'toJSON', 'toObject', + 'typePojoToMixed', 'useCreateIndex', 'useFindAndModify', 'useNewUrlParser', 'usePushEach', - 'useUnifiedTopology', - 'typePojoToMixed' + 'useUnifiedTopology' ]); module.exports = VALID_OPTIONS; diff --git a/test/model.test.js b/test/model.test.js index 6f2632f5197..6fcfb2ca6f2 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -6868,4 +6868,57 @@ describe('Model', function() { assert.deepEqual(user.friends, ['Sam']); }); }); + + describe('returnOriginal (gh-9183)', function() { + const originalValue = mongoose.get('returnOriginal'); + beforeEach(() => { + mongoose.set('returnOriginal', false); + }); + + afterEach(() => { + mongoose.set('returnOriginal', originalValue); + }); + + it('Setting `returnOriginal` works', function() { + return co(function*() { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + const createdUser = yield User.create({ name: 'Hafez' }); + + const user1 = yield User.findOneAndUpdate({ _id: createdUser._id }, { name: 'Hafez1' }); + assert.equal(user1.name, 'Hafez1'); + + const user2 = yield User.findByIdAndUpdate(createdUser._id, { name: 'Hafez2' }); + assert.equal(user2.name, 'Hafez2'); + + const user3 = yield User.findOneAndReplace({ _id: createdUser._id }, { name: 'Hafez3' }); + assert.equal(user3.name, 'Hafez3'); + }); + }); + + it('`returnOriginal` can be overwritten', function() { + return co(function*() { + const userSchema = new Schema({ + name: { type: String } + }); + + const User = db.model('User', userSchema); + + const createdUser = yield User.create({ name: 'Hafez' }); + + const user1 = yield User.findOneAndUpdate({ _id: createdUser._id }, { name: 'Hafez1' }, { new: false }); + assert.equal(user1.name, 'Hafez'); + + const user2 = yield User.findByIdAndUpdate(createdUser._id, { name: 'Hafez2' }, { new: false }); + assert.equal(user2.name, 'Hafez1'); + + const user3 = yield User.findOneAndReplace({ _id: createdUser._id }, { name: 'Hafez3' }, { new: false }); + assert.equal(user3.name, 'Hafez2'); + }); + }); + }); });