diff --git a/lib/document.js b/lib/document.js index aa7fb04be26..568fe653719 100644 --- a/lib/document.js +++ b/lib/document.js @@ -740,7 +740,13 @@ function init(self, obj, doc, opts, prefix) { if (schemaType && !wasPopulated) { try { - doc[i] = schemaType.cast(obj[i], self, true); + if (opts && opts.setters) { + // Call applySetters with `init = false` because otherwise setters are a noop + const overrideInit = false; + doc[i] = schemaType.applySetters(obj[i], self, overrideInit); + } else { + doc[i] = schemaType.cast(obj[i], self, true); + } } catch (e) { self.invalidate(e.path, new ValidatorError({ path: e.path, diff --git a/lib/model.js b/lib/model.js index bb79be729b6..9de649739e4 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3762,11 +3762,13 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op * * @param {Object} obj * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document + * @param {Object} [options] optional options + * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating * @return {Document} document instance * @api public */ -Model.hydrate = function(obj, projection) { +Model.hydrate = function(obj, projection, options) { _checkContext(this, 'hydrate'); if (projection != null) { @@ -3777,7 +3779,7 @@ Model.hydrate = function(obj, projection) { } const document = require('./queryhelpers').createModel(this, obj, projection); - document.$init(obj); + document.$init(obj, options); return document; }; diff --git a/test/model.test.js b/test/model.test.js index d4ab16ad176..01585f75b9f 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8641,6 +8641,19 @@ describe('Model', function() { assert.equal(doc.hoursToMake, null); }); + it('supports setters option for `hydrate()` (gh-11653)', function() { + const schema = Schema({ + text: { + type: String, + set: v => v.toLowerCase() + } + }); + const Test = db.model('Test', schema); + + const doc = Test.hydrate({ text: 'FOOBAR' }, null, { setters: true }); + assert.equal(doc.text, 'foobar'); + }); + it('sets index collation based on schema collation (gh-7621)', async function() { let testSchema = new Schema( { name: { type: String, index: true } } diff --git a/types/models.d.ts b/types/models.d.ts index 6396a4bebf1..9fae6afa66e 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -128,27 +128,27 @@ declare module 'mongoose' { base: Mongoose; /** - * If this is a discriminator model, `baseModelName` is the name of - * the base model. - */ + * If this is a discriminator model, `baseModelName` is the name of + * the base model. + */ baseModelName: string | undefined; /** - * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, - * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one - * command. This is faster than sending multiple independent operations (e.g. - * if you use `create()`) because with `bulkWrite()` there is only one network - * round trip to the MongoDB server. - */ + * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, + * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one + * command. This is faster than sending multiple independent operations (e.g. + * if you use `create()`) because with `bulkWrite()` there is only one network + * round trip to the MongoDB server. + */ bulkWrite(writes: Array, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback): void; bulkWrite(writes: Array, callback: Callback): void; bulkWrite(writes: Array, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; /** - * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than - * sending multiple `save()` calls because with `bulkSave()` there is only one - * network round trip to the MongoDB server. - */ + * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than + * sending multiple `save()` calls because with `bulkSave()` there is only one + * network round trip to the MongoDB server. + */ bulkSave(documents: Array, options?: mongodb.BulkWriteOptions & { timestamps?: boolean }): Promise; /** Collection the model uses. */ @@ -170,10 +170,10 @@ declare module 'mongoose' { create>(doc: T | DocContents, callback: Callback>): void; /** - * Create the collection for this model. By default, if no indexes are specified, - * mongoose will not create the collection for the model until any documents are - * created. Use this method to create the collection explicitly. - */ + * Create the collection for this model. By default, if no indexes are specified, + * mongoose will not create the collection for the model until any documents are + * created. Use this method to create the collection explicitly. + */ createCollection(options: mongodb.CreateCollectionOptions & Pick | null, callback: Callback>): void; createCollection(callback: Callback>): void; createCollection(options?: mongodb.CreateCollectionOptions & Pick): Promise>; @@ -182,34 +182,34 @@ declare module 'mongoose' { db: Connection; /** - * Deletes all of the documents that match `conditions` from the collection. - * Behaves like `remove()`, but deletes all documents that match `conditions` - * regardless of the `single` option. - */ + * Deletes all of the documents that match `conditions` from the collection. + * Behaves like `remove()`, but deletes all documents that match `conditions` + * regardless of the `single` option. + */ deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; /** - * Deletes the first document that matches `conditions` from the collection. - * Behaves like `remove()`, but deletes at most one document regardless of the - * `single` option. - */ + * Deletes the first document that matches `conditions` from the collection. + * Behaves like `remove()`, but deletes at most one document regardless of the + * `single` option. + */ deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; /** - * Event emitter that reports any errors that occurred. Useful for global error - * handling. - */ + * Event emitter that reports any errors that occurred. Useful for global error + * handling. + */ events: NodeJS.EventEmitter; /** - * Finds a single document by its _id field. `findById(id)` is almost* - * equivalent to `findOne({ _id: id })`. If you want to query by a document's - * `_id`, use `findById()` instead of `findOne()`. - */ + * Finds a single document by its _id field. `findById(id)` is almost* + * equivalent to `findOne({ _id: id })`. If you want to query by a document's + * `_id`, use `findById()` instead of `findOne()`. + */ findById>( id: any, projection?: ProjectionType | null, @@ -240,19 +240,19 @@ declare module 'mongoose' { ): QueryWithHelpers; /** - * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. - * The document returned has no paths marked as modified initially. - */ - hydrate(obj: any): HydratedDocument; + * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. + * The document returned has no paths marked as modified initially. + */ + hydrate(obj: any, projection?: AnyObject, options?: { setters?: boolean }): HydratedDocument; /** - * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), - * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. - * Mongoose calls this function automatically when a model is created using - * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or - * [`connection.model()`](/docs/api.html#connection_Connection-model), so you - * don't need to call it. - */ + * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), + * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. + * Mongoose calls this function automatically when a model is created using + * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or + * [`connection.model()`](/docs/api.html#connection_Connection-model), so you + * don't need to call it. + */ init(callback?: CallbackWithoutResult): Promise>; /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */